Access repos by their ids (#1691)

closes #1295 
closes #648

# TODO
- [x] add new routes with `:repoID`
- [x] load repo in middleware using `:repoID` if present
- [x] update UI routes `:owner/:name` to `:repoID`
- [x] load repos using id in UI
- [x] add lookup endpoint `:owner/:name` to `:repoID`
- [x] redirect `:owner/:name` to `:repoID` in UI
- [x] use badge with `:repoID` route in UI
- [x] update `woodpecker-go`
- [x] check cli
- [x] add migrations / deprecation notes
- [x] check if #648 got solved directly
- [x] Test
  - [x] create repo
  - [x] repo pages
  - [x] ui redirects
  - [x] forge status links
This commit is contained in:
Anbraten 2023-06-12 16:07:52 -07:00 committed by GitHub
parent e3593cd9a4
commit ff01a9ff1d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
98 changed files with 1402 additions and 1676 deletions

View file

@ -70,5 +70,5 @@ func FormatFlag(tmpl string, hidden ...bool) *cli.StringFlag {
var RepoFlag = &cli.StringFlag{
Name: "repository",
Aliases: []string{"repo"},
Usage: "repository name (e.g. octocat/hello-world)",
Usage: "repository id or full-name (e.g. 134 or octocat/hello-world)",
}

View file

@ -14,7 +14,7 @@ import (
var cronCreateCmd = &cli.Command{
Name: "add",
Usage: "add a cron job",
ArgsUsage: "[repo/name]",
ArgsUsage: "[repo-id|repo-full-name]",
Action: cronCreate,
Flags: append(common.GlobalFlags,
common.RepoFlag,
@ -38,29 +38,32 @@ var cronCreateCmd = &cli.Command{
func cronCreate(c *cli.Context) error {
var (
jobName = c.String("name")
branch = c.String("branch")
schedule = c.String("schedule")
reponame = c.String("repository")
format = c.String("format") + "\n"
jobName = c.String("name")
branch = c.String("branch")
schedule = c.String("schedule")
repoIDOrFullName = c.String("repository")
format = c.String("format") + "\n"
)
if reponame == "" {
reponame = c.Args().First()
}
owner, name, err := internal.ParseRepo(reponame)
if err != nil {
return err
if repoIDOrFullName == "" {
repoIDOrFullName = c.Args().First()
}
client, err := internal.NewClient(c)
if err != nil {
return err
}
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return err
}
cron := &woodpecker.Cron{
Name: jobName,
Branch: branch,
Schedule: schedule,
}
cron, err = client.CronCreate(owner, name, cron)
cron, err = client.CronCreate(repoID, cron)
if err != nil {
return err
}

View file

@ -13,7 +13,7 @@ import (
var cronInfoCmd = &cli.Command{
Name: "info",
Usage: "display info about a cron job",
ArgsUsage: "[repo/name]",
ArgsUsage: "[repo-id|repo-full-name]",
Action: cronInfo,
Flags: append(common.GlobalFlags,
common.RepoFlag,
@ -28,22 +28,23 @@ var cronInfoCmd = &cli.Command{
func cronInfo(c *cli.Context) error {
var (
jobID = c.Int64("id")
reponame = c.String("repository")
format = c.String("format") + "\n"
jobID = c.Int64("id")
repoIDOrFullName = c.String("repository")
format = c.String("format") + "\n"
)
if reponame == "" {
reponame = c.Args().First()
}
owner, name, err := internal.ParseRepo(reponame)
if err != nil {
return err
if repoIDOrFullName == "" {
repoIDOrFullName = c.Args().First()
}
client, err := internal.NewClient(c)
if err != nil {
return err
}
cron, err := client.CronGet(owner, name, jobID)
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return err
}
cron, err := client.CronGet(repoID, jobID)
if err != nil {
return err
}

View file

@ -27,7 +27,7 @@ import (
var cronListCmd = &cli.Command{
Name: "ls",
Usage: "list cron jobs",
ArgsUsage: "[repo/name]",
ArgsUsage: "[repo-id|repo-full-name]",
Action: cronList,
Flags: append(common.GlobalFlags,
common.RepoFlag,
@ -37,21 +37,21 @@ var cronListCmd = &cli.Command{
func cronList(c *cli.Context) error {
var (
format = c.String("format") + "\n"
reponame = c.String("repository")
format = c.String("format") + "\n"
repoIDOrFullName = c.String("repository")
)
if reponame == "" {
reponame = c.Args().First()
}
owner, name, err := internal.ParseRepo(reponame)
if err != nil {
return err
if repoIDOrFullName == "" {
repoIDOrFullName = c.Args().First()
}
client, err := internal.NewClient(c)
if err != nil {
return err
}
list, err := client.CronList(owner, name)
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return err
}
list, err := client.CronList(repoID)
if err != nil {
return err
}

View file

@ -12,7 +12,7 @@ import (
var cronDeleteCmd = &cli.Command{
Name: "rm",
Usage: "remove a cron job",
ArgsUsage: "[repo/name]",
ArgsUsage: "[repo-id|repo-full-name]",
Action: cronDelete,
Flags: append(common.GlobalFlags,
common.RepoFlag,
@ -26,21 +26,21 @@ var cronDeleteCmd = &cli.Command{
func cronDelete(c *cli.Context) error {
var (
jobID = c.Int64("id")
reponame = c.String("repository")
jobID = c.Int64("id")
repoIDOrFullName = c.String("repository")
)
if reponame == "" {
reponame = c.Args().First()
}
owner, name, err := internal.ParseRepo(reponame)
if err != nil {
return err
if repoIDOrFullName == "" {
repoIDOrFullName = c.Args().First()
}
client, err := internal.NewClient(c)
if err != nil {
return err
}
err = client.CronDelete(owner, name, jobID)
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return err
}
err = client.CronDelete(repoID, jobID)
if err != nil {
return err
}

View file

@ -14,7 +14,7 @@ import (
var cronUpdateCmd = &cli.Command{
Name: "update",
Usage: "update a cron job",
ArgsUsage: "[repo/name]",
ArgsUsage: "[repo-id|repo-full-name]",
Action: cronUpdate,
Flags: append(common.GlobalFlags,
common.RepoFlag,
@ -41,21 +41,21 @@ var cronUpdateCmd = &cli.Command{
func cronUpdate(c *cli.Context) error {
var (
reponame = c.String("repository")
jobID = c.Int64("id")
jobName = c.String("name")
branch = c.String("branch")
schedule = c.String("schedule")
format = c.String("format") + "\n"
repoIDOrFullName = c.String("repository")
jobID = c.Int64("id")
jobName = c.String("name")
branch = c.String("branch")
schedule = c.String("schedule")
format = c.String("format") + "\n"
)
if reponame == "" {
reponame = c.Args().First()
if repoIDOrFullName == "" {
repoIDOrFullName = c.Args().First()
}
owner, name, err := internal.ParseRepo(reponame)
client, err := internal.NewClient(c)
if err != nil {
return err
}
client, err := internal.NewClient(c)
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return err
}
@ -65,7 +65,7 @@ func cronUpdate(c *cli.Context) error {
Branch: branch,
Schedule: schedule,
}
cron, err = client.CronUpdate(owner, name, cron)
cron, err = client.CronUpdate(repoID, cron)
if err != nil {
return err
}

View file

@ -31,7 +31,7 @@ import (
var Command = &cli.Command{
Name: "deploy",
Usage: "deploy code",
ArgsUsage: "<repo/name> <pipeline> <environment>",
ArgsUsage: "<repo-id|repo-full-name> <pipeline> <environment>",
Action: deploy,
Flags: append(common.GlobalFlags,
common.FormatFlag(tmplDeployInfo),
@ -59,13 +59,13 @@ var Command = &cli.Command{
}
func deploy(c *cli.Context) error {
repo := c.Args().First()
owner, name, err := internal.ParseRepo(repo)
client, err := internal.NewClient(c)
if err != nil {
return err
}
client, err := internal.NewClient(c)
repo := c.Args().First()
repoID, err := internal.ParseRepo(client, repo)
if err != nil {
return err
}
@ -78,7 +78,7 @@ func deploy(c *cli.Context) error {
var number int
if pipelineArg == "last" {
// Fetch the pipeline number from the last pipeline
pipelines, berr := client.PipelineList(owner, name)
pipelines, berr := client.PipelineList(repoID)
if berr != nil {
return berr
}
@ -113,7 +113,7 @@ func deploy(c *cli.Context) error {
params := internal.ParseKeyPair(c.StringSlice("param"))
deploy, err := client.Deploy(owner, name, number, env, params)
deploy, err := client.Deploy(repoID, number, env, params)
if err != nil {
return err
}

View file

@ -5,6 +5,7 @@ import (
"crypto/x509"
"fmt"
"net/http"
"strconv"
"strings"
"github.com/rs/zerolog/log"
@ -76,15 +77,16 @@ func NewClient(c *cli.Context) (woodpecker.Client, error) {
}
// ParseRepo parses the repository owner and name from a string.
func ParseRepo(str string) (user, repo string, err error) {
parts := strings.Split(str, "/")
if len(parts) != 2 {
err = fmt.Errorf("Error: Invalid or missing repository. eg octocat/hello-world")
return
func ParseRepo(client woodpecker.Client, str string) (repoID int64, err error) {
if strings.Contains(str, "/") {
repo, err := client.RepoLookup(str)
if err != nil {
return 0, err
}
return repo.ID, nil
}
user = parts[0]
repo = parts[1]
return
return strconv.ParseInt(str, 10, 64)
}
// ParseKeyPair parses a key=value pair.

View file

@ -27,14 +27,18 @@ import (
var logPurgeCmd = &cli.Command{
Name: "purge",
Usage: "purge a log",
ArgsUsage: "<repo/name> <pipeline>",
ArgsUsage: "<repo-id|repo-full-name> <pipeline>",
Action: logPurge,
Flags: common.GlobalFlags,
}
func logPurge(c *cli.Context) (err error) {
repo := c.Args().First()
owner, name, err := internal.ParseRepo(repo)
client, err := internal.NewClient(c)
if err != nil {
return err
}
repoIDOrFullName := c.Args().First()
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return err
}
@ -43,16 +47,11 @@ func logPurge(c *cli.Context) (err error) {
return err
}
client, err := internal.NewClient(c)
err = client.LogsPurge(repoID, number)
if err != nil {
return err
}
err = client.LogsPurge(owner, name, number)
if err != nil {
return err
}
fmt.Printf("Purging logs for pipeline %s/%s#%d\n", owner, name, number)
fmt.Printf("Purging logs for pipeline %s#%d\n", repoIDOrFullName, number)
return nil
}

View file

@ -27,14 +27,18 @@ import (
var pipelineApproveCmd = &cli.Command{
Name: "approve",
Usage: "approve a pipeline",
ArgsUsage: "<repo/name> <pipeline>",
ArgsUsage: "<repo-id|repo-full-name> <pipeline>",
Action: pipelineApprove,
Flags: common.GlobalFlags,
}
func pipelineApprove(c *cli.Context) (err error) {
repo := c.Args().First()
owner, name, err := internal.ParseRepo(repo)
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c)
if err != nil {
return err
}
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return err
}
@ -43,16 +47,11 @@ func pipelineApprove(c *cli.Context) (err error) {
return err
}
client, err := internal.NewClient(c)
_, err = client.PipelineApprove(repoID, number)
if err != nil {
return err
}
_, err = client.PipelineApprove(owner, name, number)
if err != nil {
return err
}
fmt.Printf("Approving pipeline %s/%s#%d\n", owner, name, number)
fmt.Printf("Approving pipeline %s#%d\n", repoIDOrFullName, number)
return nil
}

View file

@ -30,7 +30,7 @@ import (
var pipelineCreateCmd = &cli.Command{
Name: "create",
Usage: "create new pipeline",
ArgsUsage: "<repo/name>",
ArgsUsage: "<repo-id|repo-full-name>",
Action: pipelineCreate,
Flags: append(common.GlobalFlags,
common.FormatFlag(tmplPipelineList),
@ -47,14 +47,12 @@ var pipelineCreateCmd = &cli.Command{
}
func pipelineCreate(c *cli.Context) error {
repo := c.Args().First()
owner, name, err := internal.ParseRepo(repo)
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c)
if err != nil {
return err
}
client, err := internal.NewClient(c)
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return err
}
@ -74,7 +72,7 @@ func pipelineCreate(c *cli.Context) error {
Variables: variables,
}
pipeline, err := client.PipelineCreate(owner, name, options)
pipeline, err := client.PipelineCreate(repoID, options)
if err != nil {
return err
}

View file

@ -27,32 +27,32 @@ import (
var pipelineDeclineCmd = &cli.Command{
Name: "decline",
Usage: "decline a pipeline",
ArgsUsage: "<repo/name> <pipeline>",
ArgsUsage: "<repo-id|repo-full-name> <pipeline>",
Action: pipelineDecline,
Flags: common.GlobalFlags,
}
func pipelineDecline(c *cli.Context) (err error) {
repo := c.Args().First()
owner, name, err := internal.ParseRepo(repo)
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c)
if err != nil {
return err
}
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return err
}
number, err := strconv.Atoi(c.Args().Get(1))
if err != nil {
return err
}
client, err := internal.NewClient(c)
_, err = client.PipelineDecline(repoID, number)
if err != nil {
return err
}
_, err = client.PipelineDecline(owner, name, number)
if err != nil {
return err
}
fmt.Printf("Declining pipeline %s/%s#%d\n", owner, name, number)
fmt.Printf("Declining pipeline %s#%d\n", repoIDOrFullName, number)
return nil
}

View file

@ -28,7 +28,7 @@ import (
var pipelineInfoCmd = &cli.Command{
Name: "info",
Usage: "show pipeline details",
ArgsUsage: "<repo/name> [pipeline]",
ArgsUsage: "<repo-id|repo-full-name> [pipeline]",
Action: pipelineInfo,
Flags: append(common.GlobalFlags,
common.FormatFlag(tmplPipelineInfo),
@ -36,22 +36,21 @@ var pipelineInfoCmd = &cli.Command{
}
func pipelineInfo(c *cli.Context) error {
repo := c.Args().First()
owner, name, err := internal.ParseRepo(repo)
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c)
if err != nil {
return err
}
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return err
}
pipelineArg := c.Args().Get(1)
client, err := internal.NewClient(c)
if err != nil {
return err
}
var number int
if pipelineArg == "last" || len(pipelineArg) == 0 {
// Fetch the pipeline number from the last pipeline
pipeline, err := client.PipelineLast(owner, name, "")
pipeline, err := client.PipelineLast(repoID, "")
if err != nil {
return err
}
@ -63,7 +62,7 @@ func pipelineInfo(c *cli.Context) error {
}
}
pipeline, err := client.Pipeline(owner, name, number)
pipeline, err := client.Pipeline(repoID, number)
if err != nil {
return err
}

View file

@ -27,33 +27,33 @@ import (
var pipelineKillCmd = &cli.Command{
Name: "kill",
Usage: "force kill a pipeline",
ArgsUsage: "<repo/name> <pipeline>",
ArgsUsage: "<repo-id|repo-full-name> <pipeline>",
Action: pipelineKill,
Hidden: true,
Flags: common.GlobalFlags,
}
func pipelineKill(c *cli.Context) (err error) {
repo := c.Args().First()
owner, name, err := internal.ParseRepo(repo)
if err != nil {
return err
}
number, err := strconv.Atoi(c.Args().Get(1))
if err != nil {
return err
}
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c)
if err != nil {
return err
}
err = client.PipelineKill(owner, name, number)
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return err
}
fmt.Printf("Force killing pipeline %s/%s#%d\n", owner, name, number)
err = client.PipelineKill(repoID, number)
if err != nil {
return err
}
fmt.Printf("Force killing pipeline %s#%d\n", repoIDOrFullName, number)
return nil
}

View file

@ -27,7 +27,7 @@ import (
var pipelineLastCmd = &cli.Command{
Name: "last",
Usage: "show latest pipeline details",
ArgsUsage: "<repo/name>",
ArgsUsage: "<repo-id|repo-full-name>",
Action: pipelineLast,
Flags: append(common.GlobalFlags,
common.FormatFlag(tmplPipelineInfo),
@ -40,18 +40,17 @@ var pipelineLastCmd = &cli.Command{
}
func pipelineLast(c *cli.Context) error {
repo := c.Args().First()
owner, name, err := internal.ParseRepo(repo)
if err != nil {
return err
}
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c)
if err != nil {
return err
}
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return err
}
pipeline, err := client.PipelineLast(owner, name, c.String("branch"))
pipeline, err := client.PipelineLast(repoID, c.String("branch"))
if err != nil {
return err
}

View file

@ -27,7 +27,7 @@ import (
var pipelineListCmd = &cli.Command{
Name: "ls",
Usage: "show pipeline history",
ArgsUsage: "<repo/name>",
ArgsUsage: "<repo-id|repo-full-name>",
Action: pipelineList,
Flags: append(common.GlobalFlags,
common.FormatFlag(tmplPipelineList),
@ -52,18 +52,17 @@ var pipelineListCmd = &cli.Command{
}
func pipelineList(c *cli.Context) error {
repo := c.Args().First()
owner, name, err := internal.ParseRepo(repo)
if err != nil {
return err
}
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c)
if err != nil {
return err
}
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return err
}
pipelines, err := client.PipelineList(owner, name)
pipelines, err := client.PipelineList(repoID)
if err != nil {
return err
}
@ -101,7 +100,7 @@ func pipelineList(c *cli.Context) error {
}
// template for pipeline list information
var tmplPipelineList = "\x1b[33mBuild #{{ .Number }} \x1b[0m" + `
var tmplPipelineList = "\x1b[33mPipeline #{{ .Number }} \x1b[0m" + `
Status: {{ .Status }}
Event: {{ .Event }}
Commit: {{ .Commit }}

View file

@ -27,14 +27,18 @@ import (
var pipelineLogsCmd = &cli.Command{
Name: "logs",
Usage: "show pipeline logs",
ArgsUsage: "<repo/name> [pipeline] [stepID]",
ArgsUsage: "<repo-id|repo-full-name> [pipeline] [stepID]",
Action: pipelineLogs,
Flags: common.GlobalFlags,
}
func pipelineLogs(c *cli.Context) error {
repo := c.Args().First()
owner, name, err := internal.ParseRepo(repo)
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c)
if err != nil {
return err
}
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return err
}
@ -49,12 +53,7 @@ func pipelineLogs(c *cli.Context) error {
return err
}
client, err := internal.NewClient(c)
if err != nil {
return err
}
logs, err := client.StepLogEntries(owner, name, number, step)
logs, err := client.StepLogEntries(repoID, number, step)
if err != nil {
return err
}

View file

@ -28,7 +28,7 @@ import (
var pipelinePsCmd = &cli.Command{
Name: "ps",
Usage: "show pipeline steps",
ArgsUsage: "<repo/name> [pipeline]",
ArgsUsage: "<repo-id|repo-full-name> [pipeline]",
Action: pipelinePs,
Flags: append(common.GlobalFlags,
common.FormatFlag(tmplPipelinePs),
@ -36,14 +36,12 @@ var pipelinePsCmd = &cli.Command{
}
func pipelinePs(c *cli.Context) error {
repo := c.Args().First()
owner, name, err := internal.ParseRepo(repo)
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c)
if err != nil {
return err
}
client, err := internal.NewClient(c)
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return err
}
@ -53,7 +51,7 @@ func pipelinePs(c *cli.Context) error {
if pipelineArg == "last" || len(pipelineArg) == 0 {
// Fetch the pipeline number from the last pipeline
pipeline, err := client.PipelineLast(owner, name, "")
pipeline, err := client.PipelineLast(repoID, "")
if err != nil {
return err
}
@ -66,7 +64,7 @@ func pipelinePs(c *cli.Context) error {
}
}
pipeline, err := client.Pipeline(owner, name, number)
pipeline, err := client.Pipeline(repoID, number)
if err != nil {
return err
}

View file

@ -28,7 +28,7 @@ import (
var pipelineStartCmd = &cli.Command{
Name: "start",
Usage: "start a pipeline",
ArgsUsage: "<repo/name> [pipeline]",
ArgsUsage: "<repo-id|repo-full-name> [pipeline]",
Action: pipelineStart,
Flags: append(common.GlobalFlags,
&cli.StringSliceFlag{
@ -40,13 +40,12 @@ var pipelineStartCmd = &cli.Command{
}
func pipelineStart(c *cli.Context) (err error) {
repo := c.Args().First()
owner, name, err := internal.ParseRepo(repo)
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c)
if err != nil {
return err
}
client, err := internal.NewClient(c)
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return err
}
@ -55,7 +54,7 @@ func pipelineStart(c *cli.Context) (err error) {
var number int
if pipelineArg == "last" {
// Fetch the pipeline number from the last pipeline
pipeline, err := client.PipelineLast(owner, name, "")
pipeline, err := client.PipelineLast(repoID, "")
if err != nil {
return err
}
@ -72,11 +71,11 @@ func pipelineStart(c *cli.Context) (err error) {
params := internal.ParseKeyPair(c.StringSlice("param"))
pipeline, err := client.PipelineStart(owner, name, number, params)
pipeline, err := client.PipelineStart(repoID, number, params)
if err != nil {
return err
}
fmt.Printf("Starting pipeline %s/%s#%d\n", owner, name, pipeline.Number)
fmt.Printf("Starting pipeline %s#%d\n", repoIDOrFullName, pipeline.Number)
return nil
}

View file

@ -27,14 +27,18 @@ import (
var pipelineStopCmd = &cli.Command{
Name: "stop",
Usage: "stop a pipeline",
ArgsUsage: "<repo/name> [pipeline]",
ArgsUsage: "<repo-id|repo-full-name> [pipeline]",
Flags: common.GlobalFlags,
Action: pipelineStop,
}
func pipelineStop(c *cli.Context) (err error) {
repo := c.Args().First()
owner, name, err := internal.ParseRepo(repo)
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c)
if err != nil {
return err
}
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return err
}
@ -43,16 +47,11 @@ func pipelineStop(c *cli.Context) (err error) {
return err
}
client, err := internal.NewClient(c)
err = client.PipelineStop(repoID, number)
if err != nil {
return err
}
err = client.PipelineStop(owner, name, number)
if err != nil {
return err
}
fmt.Printf("Stopping pipeline %s/%s#%d\n", owner, name, number)
fmt.Printf("Stopping pipeline %s#%d\n", repoIDOrFullName, number)
return nil
}

View file

@ -14,7 +14,7 @@ import (
var registryCreateCmd = &cli.Command{
Name: "add",
Usage: "adds a registry",
ArgsUsage: "[repo/name]",
ArgsUsage: "[repo-id|repo-full-name]",
Action: registryCreate,
Flags: append(common.GlobalFlags,
common.RepoFlag,
@ -36,19 +36,19 @@ var registryCreateCmd = &cli.Command{
func registryCreate(c *cli.Context) error {
var (
hostname = c.String("hostname")
username = c.String("username")
password = c.String("password")
reponame = c.String("repository")
hostname = c.String("hostname")
username = c.String("username")
password = c.String("password")
repoIDOrFullName = c.String("repository")
)
if reponame == "" {
reponame = c.Args().First()
if repoIDOrFullName == "" {
repoIDOrFullName = c.Args().First()
}
owner, name, err := internal.ParseRepo(reponame)
client, err := internal.NewClient(c)
if err != nil {
return err
}
client, err := internal.NewClient(c)
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return err
}
@ -65,7 +65,7 @@ func registryCreate(c *cli.Context) error {
}
registry.Password = string(out)
}
_, err = client.RegistryCreate(owner, name, registry)
_, err = client.RegistryCreate(repoID, registry)
if err != nil {
return err
}

View file

@ -13,7 +13,7 @@ import (
var registryInfoCmd = &cli.Command{
Name: "info",
Usage: "display registry info",
ArgsUsage: "[repo/name]",
ArgsUsage: "[repo-id|repo-full-name]",
Action: registryInfo,
Flags: append(common.GlobalFlags,
common.RepoFlag,
@ -28,22 +28,22 @@ var registryInfoCmd = &cli.Command{
func registryInfo(c *cli.Context) error {
var (
hostname = c.String("hostname")
reponame = c.String("repository")
format = c.String("format") + "\n"
hostname = c.String("hostname")
repoIDOrFullName = c.String("repository")
format = c.String("format") + "\n"
)
if reponame == "" {
reponame = c.Args().First()
}
owner, name, err := internal.ParseRepo(reponame)
if err != nil {
return err
if repoIDOrFullName == "" {
repoIDOrFullName = c.Args().First()
}
client, err := internal.NewClient(c)
if err != nil {
return err
}
registry, err := client.Registry(owner, name, hostname)
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return err
}
registry, err := client.Registry(repoID, hostname)
if err != nil {
return err
}

View file

@ -27,7 +27,7 @@ import (
var registryListCmd = &cli.Command{
Name: "ls",
Usage: "list registries",
ArgsUsage: "[repo/name]",
ArgsUsage: "[repo-id|repo-full-name]",
Action: registryList,
Flags: append(common.GlobalFlags,
common.RepoFlag,
@ -37,21 +37,21 @@ var registryListCmd = &cli.Command{
func registryList(c *cli.Context) error {
var (
format = c.String("format") + "\n"
reponame = c.String("repository")
format = c.String("format") + "\n"
repoIDOrFullName = c.String("repository")
)
if reponame == "" {
reponame = c.Args().First()
}
owner, name, err := internal.ParseRepo(reponame)
if err != nil {
return err
if repoIDOrFullName == "" {
repoIDOrFullName = c.Args().First()
}
client, err := internal.NewClient(c)
if err != nil {
return err
}
list, err := client.RegistryList(owner, name)
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return err
}
list, err := client.RegistryList(repoID)
if err != nil {
return err
}

View file

@ -10,7 +10,7 @@ import (
var registryDeleteCmd = &cli.Command{
Name: "rm",
Usage: "remove a registry",
ArgsUsage: "[repo/name]",
ArgsUsage: "[repo-id|repo-full-name]",
Action: registryDelete,
Flags: append(common.GlobalFlags,
common.RepoFlag,
@ -24,19 +24,19 @@ var registryDeleteCmd = &cli.Command{
func registryDelete(c *cli.Context) error {
var (
hostname = c.String("hostname")
reponame = c.String("repository")
hostname = c.String("hostname")
repoIDOrFullName = c.String("repository")
)
if reponame == "" {
reponame = c.Args().First()
}
owner, name, err := internal.ParseRepo(reponame)
if err != nil {
return err
if repoIDOrFullName == "" {
repoIDOrFullName = c.Args().First()
}
client, err := internal.NewClient(c)
if err != nil {
return err
}
return client.RegistryDelete(owner, name, hostname)
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return err
}
return client.RegistryDelete(repoID, hostname)
}

View file

@ -14,7 +14,7 @@ import (
var registryUpdateCmd = &cli.Command{
Name: "update",
Usage: "update a registry",
ArgsUsage: "[repo/name]",
ArgsUsage: "[repo-id|repo-full-name]",
Action: registryUpdate,
Flags: append(common.GlobalFlags,
common.RepoFlag,
@ -36,19 +36,19 @@ var registryUpdateCmd = &cli.Command{
func registryUpdate(c *cli.Context) error {
var (
hostname = c.String("hostname")
username = c.String("username")
password = c.String("password")
reponame = c.String("repository")
hostname = c.String("hostname")
username = c.String("username")
password = c.String("password")
repoIDOrFullName = c.String("repository")
)
if reponame == "" {
reponame = c.Args().First()
if repoIDOrFullName == "" {
repoIDOrFullName = c.Args().First()
}
owner, name, err := internal.ParseRepo(reponame)
client, err := internal.NewClient(c)
if err != nil {
return err
}
client, err := internal.NewClient(c)
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return err
}
@ -65,9 +65,6 @@ func registryUpdate(c *cli.Context) error {
}
registry.Password = string(out)
}
_, err = client.RegistryUpdate(owner, name, registry)
if err != nil {
return err
}
return nil
_, err = client.RegistryUpdate(repoID, registry)
return err
}

View file

@ -2,6 +2,7 @@ package repo
import (
"fmt"
"strconv"
"github.com/urfave/cli/v2"
@ -12,16 +13,16 @@ import (
var repoAddCmd = &cli.Command{
Name: "add",
Usage: "add a repository",
ArgsUsage: "<repo/name>",
ArgsUsage: "<forge-remote-id>",
Action: repoAdd,
Flags: common.GlobalFlags,
}
func repoAdd(c *cli.Context) error {
repo := c.Args().First()
owner, name, err := internal.ParseRepo(repo)
_forgeRemoteID := c.Args().First()
forgeRemoteID, err := strconv.Atoi(_forgeRemoteID)
if err != nil {
return err
return fmt.Errorf("invalid forge remote id: %s", _forgeRemoteID)
}
client, err := internal.NewClient(c)
@ -29,9 +30,11 @@ func repoAdd(c *cli.Context) error {
return err
}
if _, err := client.RepoPost(owner, name); err != nil {
repo, err := client.RepoPost(int64(forgeRemoteID))
if err != nil {
return err
}
fmt.Printf("Successfully activated repository %s/%s\n", owner, name)
fmt.Printf("Successfully activated repository with forge remote %s\n", repo.FullName)
return nil
}

View file

@ -12,26 +12,27 @@ import (
var repoChownCmd = &cli.Command{
Name: "chown",
Usage: "assume ownership of a repository",
ArgsUsage: "<repo/name>",
ArgsUsage: "<repo-id|repo-full-name>",
Action: repoChown,
Flags: common.GlobalFlags,
}
func repoChown(c *cli.Context) error {
repo := c.Args().First()
owner, name, err := internal.ParseRepo(repo)
if err != nil {
return err
}
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c)
if err != nil {
return err
}
if _, err := client.RepoChown(owner, name); err != nil {
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return err
}
fmt.Printf("Successfully assumed ownership of repository %s/%s\n", owner, name)
repo, err := client.RepoChown(repoID)
if err != nil {
return err
}
fmt.Printf("Successfully assumed ownership of repository %s\n", repo.FullName)
return nil
}

View file

@ -13,7 +13,7 @@ import (
var repoInfoCmd = &cli.Command{
Name: "info",
Usage: "show repository details",
ArgsUsage: "<repo/name>",
ArgsUsage: "<repo-id|repo-full-name>",
Action: repoInfo,
Flags: append(common.GlobalFlags,
common.FormatFlag(tmplRepoInfo),
@ -21,18 +21,17 @@ var repoInfoCmd = &cli.Command{
}
func repoInfo(c *cli.Context) error {
arg := c.Args().First()
owner, name, err := internal.ParseRepo(arg)
if err != nil {
return err
}
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c)
if err != nil {
return err
}
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return err
}
repo, err := client.Repo(owner, name)
repo, err := client.Repo(repoID)
if err != nil {
return err
}
@ -47,11 +46,12 @@ func repoInfo(c *cli.Context) error {
// template for repo information
var tmplRepoInfo = `Owner: {{ .Owner }}
Repo: {{ .Name }}
Type: {{ .Kind }}
Config: {{ .Config }}
Link: {{ .Link }}
Config path: {{ .Config }}
Visibility: {{ .Visibility }}
Private: {{ .IsSCMPrivate }}
Trusted: {{ .IsTrusted }}
Gated: {{ .IsGated }}
Forge: {{ .Clone }}
Clone url: {{ .Clone }}
Allow pull-requests: {{ .AllowPullRequests }}
`

View file

@ -53,4 +53,4 @@ func repoList(c *cli.Context) error {
}
// template for repository list items
var tmplRepoList = `{{ .FullName }}`
var tmplRepoList = "\x1b[33m{{ .FullName }}\x1b[0m (id: {{ .ID }})"

View file

@ -1,6 +1,8 @@
package repo
import (
"fmt"
"github.com/urfave/cli/v2"
"github.com/woodpecker-ci/woodpecker/cli/common"
@ -10,20 +12,26 @@ import (
var repoRepairCmd = &cli.Command{
Name: "repair",
Usage: "repair repository webhooks",
ArgsUsage: "<repo/name>",
ArgsUsage: "<repo-id|repo-full-name>",
Action: repoRepair,
Flags: common.GlobalFlags,
}
func repoRepair(c *cli.Context) error {
repo := c.Args().First()
owner, name, err := internal.ParseRepo(repo)
if err != nil {
return err
}
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c)
if err != nil {
return err
}
return client.RepoRepair(owner, name)
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return err
}
if err := client.RepoRepair(repoID); err != nil {
return err
}
fmt.Printf("Successfully removed repository %s\n", repoIDOrFullName)
return nil
}

View file

@ -12,26 +12,25 @@ import (
var repoRemoveCmd = &cli.Command{
Name: "rm",
Usage: "remove a repository",
ArgsUsage: "<repo/name>",
ArgsUsage: "<repo-id|repo-full-name>",
Action: repoRemove,
Flags: common.GlobalFlags,
}
func repoRemove(c *cli.Context) error {
repo := c.Args().First()
owner, name, err := internal.ParseRepo(repo)
if err != nil {
return err
}
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c)
if err != nil {
return err
}
if err := client.RepoDel(owner, name); err != nil {
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return err
}
fmt.Printf("Successfully removed repository %s/%s\n", owner, name)
if err := client.RepoDel(repoID); err != nil {
return err
}
fmt.Printf("Successfully removed repository %s\n", repoIDOrFullName)
return nil
}

View file

@ -20,6 +20,7 @@ var repoSyncCmd = &cli.Command{
),
}
// TODO: remove this and add an option to the list cmd as we do not store the remote repo list anymore
func repoSync(c *cli.Context) error {
client, err := internal.NewClient(c)
if err != nil {

View file

@ -28,7 +28,7 @@ import (
var repoUpdateCmd = &cli.Command{
Name: "update",
Usage: "update a repository",
ArgsUsage: "<repo/name>",
ArgsUsage: "<repo-id|repo-full-name>",
Action: repoUpdate,
Flags: append(common.GlobalFlags,
&cli.BoolFlag{
@ -63,13 +63,12 @@ var repoUpdateCmd = &cli.Command{
}
func repoUpdate(c *cli.Context) error {
repo := c.Args().First()
owner, name, err := internal.ParseRepo(repo)
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c)
if err != nil {
return err
}
client, err := internal.NewClient(c)
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return err
}
@ -111,9 +110,11 @@ func repoUpdate(c *cli.Context) error {
patch.PipelineCounter = &pipelineCounter
}
if _, err := client.RepoPatch(owner, name, patch); err != nil {
repo, err := client.RepoPatch(repoID, patch)
if err != nil {
return err
}
fmt.Printf("Successfully updated repository %s/%s\n", owner, name)
fmt.Printf("Successfully updated repository %s\n", repo.FullName)
return nil
}

View file

@ -7,6 +7,7 @@ import (
"github.com/woodpecker-ci/woodpecker/cli/common"
"github.com/woodpecker-ci/woodpecker/cli/internal"
"github.com/woodpecker-ci/woodpecker/woodpecker-go/woodpecker"
)
// Command exports the secret command.
@ -23,24 +24,29 @@ var Command = &cli.Command{
},
}
func parseTargetArgs(c *cli.Context) (global bool, owner, name string, err error) {
func parseTargetArgs(client woodpecker.Client, c *cli.Context) (global bool, owner string, repoID int64, err error) {
if c.Bool("global") {
return true, "", "", nil
return true, "", -1, nil
}
repoIDOrFullName := c.String("repository")
if repoIDOrFullName == "" {
repoIDOrFullName = c.Args().First()
}
orgName := c.String("organization")
repoName := c.String("repository")
if orgName == "" && repoName == "" {
repoName = c.Args().First()
if orgName != "" && repoIDOrFullName == "" {
return false, orgName, -1, err
}
if orgName == "" && !strings.Contains(repoName, "/") {
orgName = repoName
if orgName != "" && !strings.Contains(repoIDOrFullName, "/") {
repoIDOrFullName = orgName + "/" + repoIDOrFullName
}
if orgName != "" {
return false, orgName, "", err
}
owner, name, err = internal.ParseRepo(repoName)
repoID, err = internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return false, "", "", err
return false, "", -1, err
}
return false, owner, name, nil
return false, "", repoID, nil
}

View file

@ -14,7 +14,7 @@ import (
var secretCreateCmd = &cli.Command{
Name: "add",
Usage: "adds a secret",
ArgsUsage: "[org/repo|org]",
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretCreate,
Flags: append(common.GlobalFlags,
&cli.BoolFlag{
@ -74,7 +74,7 @@ func secretCreate(c *cli.Context) error {
secret.Value = string(out)
}
global, owner, repo, err := parseTargetArgs(c)
global, owner, repoID, err := parseTargetArgs(client, c)
if err != nil {
return err
}
@ -83,11 +83,11 @@ func secretCreate(c *cli.Context) error {
_, err = client.GlobalSecretCreate(secret)
return err
}
if repo == "" {
if owner != "" {
_, err = client.OrgSecretCreate(owner, secret)
return err
}
_, err = client.SecretCreate(owner, repo, secret)
_, err = client.SecretCreate(repoID, secret)
return err
}

View file

@ -14,7 +14,7 @@ import (
var secretInfoCmd = &cli.Command{
Name: "info",
Usage: "display secret info",
ArgsUsage: "[org/repo|org]",
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretInfo,
Flags: append(common.GlobalFlags,
&cli.BoolFlag{
@ -44,7 +44,7 @@ func secretInfo(c *cli.Context) error {
return err
}
global, owner, repo, err := parseTargetArgs(c)
global, owner, repoID, err := parseTargetArgs(client, c)
if err != nil {
return err
}
@ -55,13 +55,13 @@ func secretInfo(c *cli.Context) error {
if err != nil {
return err
}
} else if repo == "" {
} else if owner != "" {
secret, err = client.OrgSecret(owner, secretName)
if err != nil {
return err
}
} else {
secret, err = client.Secret(owner, repo, secretName)
secret, err = client.Secret(repoID, secretName)
if err != nil {
return err
}

View file

@ -15,7 +15,7 @@ import (
var secretListCmd = &cli.Command{
Name: "ls",
Usage: "list secrets",
ArgsUsage: "[org/name|org]",
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretList,
Flags: append(common.GlobalFlags,
&cli.BoolFlag{
@ -39,7 +39,7 @@ func secretList(c *cli.Context) error {
return err
}
global, owner, repo, err := parseTargetArgs(c)
global, owner, repoID, err := parseTargetArgs(client, c)
if err != nil {
return err
}
@ -50,13 +50,13 @@ func secretList(c *cli.Context) error {
if err != nil {
return err
}
} else if repo == "" {
} else if owner != "" {
list, err = client.OrgSecretList(owner)
if err != nil {
return err
}
} else {
list, err = client.SecretList(owner, repo)
list, err = client.SecretList(repoID)
if err != nil {
return err
}

View file

@ -10,7 +10,7 @@ import (
var secretDeleteCmd = &cli.Command{
Name: "rm",
Usage: "remove a secret",
ArgsUsage: "[org/repo|org]",
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretDelete,
Flags: append(common.GlobalFlags,
&cli.BoolFlag{
@ -37,7 +37,7 @@ func secretDelete(c *cli.Context) error {
return err
}
global, owner, repo, err := parseTargetArgs(c)
global, owner, repoID, err := parseTargetArgs(client, c)
if err != nil {
return err
}
@ -45,8 +45,8 @@ func secretDelete(c *cli.Context) error {
if global {
return client.GlobalSecretDelete(secretName)
}
if repo == "" {
if owner != "" {
return client.OrgSecretDelete(owner, secretName)
}
return client.SecretDelete(owner, repo, secretName)
return client.SecretDelete(repoID, secretName)
}

View file

@ -14,7 +14,7 @@ import (
var secretUpdateCmd = &cli.Command{
Name: "update",
Usage: "update a secret",
ArgsUsage: "[org/repo|org]",
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretUpdate,
Flags: append(common.GlobalFlags,
&cli.BoolFlag{
@ -71,7 +71,7 @@ func secretUpdate(c *cli.Context) error {
secret.Value = string(out)
}
global, owner, repo, err := parseTargetArgs(c)
global, owner, repoID, err := parseTargetArgs(client, c)
if err != nil {
return err
}
@ -80,10 +80,10 @@ func secretUpdate(c *cli.Context) error {
_, err = client.GlobalSecretUpdate(secret)
return err
}
if repo == "" {
if owner != "" {
_, err = client.OrgSecretUpdate(owner, secret)
return err
}
_, err = client.SecretUpdate(owner, repo, secret)
_, err = client.SecretUpdate(repoID, secret)
return err
}

View file

@ -250,7 +250,7 @@ const docTemplate = `{
}
}
},
"/badges/{owner}/{name}/cc.xml": {
"/badges/{repo_id}/cc.xml": {
"get": {
"description": "CCMenu displays the pipeline status of projects on a CI server as an item in the Mac's menu bar.\nMore details on how to install, you can find at http://ccmenu.org/\nThe response format adheres to CCTray v1 Specification, https://cctray.org/v1/",
"produces": [
@ -283,7 +283,7 @@ const docTemplate = `{
}
}
},
"/badges/{owner}/{name}/status.svg": {
"/badges/{repo_id}/status.svg": {
"get": {
"produces": [
"image/svg+xml"
@ -294,16 +294,9 @@ const docTemplate = `{
"summary": "Get status badge, SVG format",
"parameters": [
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
}
@ -751,7 +744,7 @@ const docTemplate = `{
}
}
},
"/logs/{owner}/{name}/{pipeline}/{stepID}": {
"/logs/{repo_id}/{pipeline}/{stepID}": {
"get": {
"produces": [
"text/plain"
@ -762,16 +755,9 @@ const docTemplate = `{
"summary": "Log stream",
"parameters": [
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
},
@ -1206,7 +1192,43 @@ const docTemplate = `{
}
}
},
"/repos/{owner}/{name}": {
"/repos/lookup/{repo_full_name}": {
"get": {
"produces": [
"application/json"
],
"tags": [
"Repositories"
],
"summary": "Get repository by full-name",
"parameters": [
{
"type": "string",
"default": "Bearer \u003cpersonal access token\u003e",
"description": "Insert your personal access token",
"name": "Authorization",
"in": "header",
"required": true
},
{
"type": "string",
"description": "the repository full-name / slug",
"name": "repo_full_name",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/Repo"
}
}
}
}
},
"/repos/{repo_id}": {
"get": {
"produces": [
"application/json"
@ -1225,16 +1247,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
}
@ -1266,16 +1281,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
}
@ -1307,16 +1315,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
}
@ -1348,16 +1349,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
},
@ -1381,7 +1375,7 @@ const docTemplate = `{
}
}
},
"/repos/{owner}/{name}/branches": {
"/repos/{repo_id}/branches": {
"get": {
"produces": [
"application/json"
@ -1400,16 +1394,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
},
@ -1441,7 +1428,7 @@ const docTemplate = `{
}
}
},
"/repos/{owner}/{name}/chown": {
"/repos/{repo_id}/chown": {
"post": {
"produces": [
"application/json"
@ -1460,16 +1447,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
}
@ -1484,7 +1464,7 @@ const docTemplate = `{
}
}
},
"/repos/{owner}/{name}/cron": {
"/repos/{repo_id}/cron": {
"get": {
"produces": [
"application/json"
@ -1503,16 +1483,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
},
@ -1561,16 +1534,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
},
@ -1594,7 +1560,7 @@ const docTemplate = `{
}
}
},
"/repos/{owner}/{name}/cron/{cron}": {
"/repos/{repo_id}/cron/{cron}": {
"get": {
"produces": [
"application/json"
@ -1613,16 +1579,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
},
@ -1661,16 +1620,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
},
@ -1709,16 +1661,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
},
@ -1754,16 +1699,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
},
@ -1794,7 +1732,7 @@ const docTemplate = `{
}
}
},
"/repos/{owner}/{name}/logs/{number}": {
"/repos/{repo_id}/logs/{number}": {
"post": {
"produces": [
"text/plain"
@ -1813,16 +1751,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
},
@ -1841,7 +1772,7 @@ const docTemplate = `{
}
}
},
"/repos/{owner}/{name}/logs/{number}/{stepID}": {
"/repos/{repo_id}/logs/{number}/{stepID}": {
"get": {
"produces": [
"application/json"
@ -1860,16 +1791,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
},
@ -1901,7 +1825,7 @@ const docTemplate = `{
}
}
},
"/repos/{owner}/{name}/move": {
"/repos/{repo_id}/move": {
"post": {
"produces": [
"text/plain"
@ -1920,16 +1844,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
},
@ -1948,7 +1865,7 @@ const docTemplate = `{
}
}
},
"/repos/{owner}/{name}/permissions": {
"/repos/{repo_id}/permissions": {
"get": {
"description": "The repository permission, according to the used access token.",
"produces": [
@ -1992,7 +1909,7 @@ const docTemplate = `{
}
}
},
"/repos/{owner}/{name}/pipelines": {
"/repos/{repo_id}/pipelines": {
"get": {
"produces": [
"application/json"
@ -2011,16 +1928,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
},
@ -2069,16 +1979,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
},
@ -2102,7 +2005,7 @@ const docTemplate = `{
}
}
},
"/repos/{owner}/{name}/pipelines/{number}": {
"/repos/{repo_id}/pipelines/{number}": {
"get": {
"produces": [
"application/json"
@ -2121,16 +2024,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
},
@ -2213,7 +2109,7 @@ const docTemplate = `{
}
}
},
"/repos/{owner}/{name}/pipelines/{number}/approve": {
"/repos/{repo_id}/pipelines/{number}/approve": {
"post": {
"produces": [
"application/json"
@ -2232,16 +2128,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
},
@ -2263,7 +2152,7 @@ const docTemplate = `{
}
}
},
"/repos/{owner}/{name}/pipelines/{number}/cancel": {
"/repos/{repo_id}/pipelines/{number}/cancel": {
"post": {
"produces": [
"text/plain"
@ -2282,16 +2171,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
},
@ -2310,7 +2192,7 @@ const docTemplate = `{
}
}
},
"/repos/{owner}/{name}/pipelines/{number}/config": {
"/repos/{repo_id}/pipelines/{number}/config": {
"get": {
"produces": [
"application/json"
@ -2329,16 +2211,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
},
@ -2363,7 +2238,7 @@ const docTemplate = `{
}
}
},
"/repos/{owner}/{name}/pipelines/{number}/decline": {
"/repos/{repo_id}/pipelines/{number}/decline": {
"post": {
"produces": [
"application/json"
@ -2382,16 +2257,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
},
@ -2413,7 +2281,7 @@ const docTemplate = `{
}
}
},
"/repos/{owner}/{name}/pull_requests": {
"/repos/{repo_id}/pull_requests": {
"get": {
"produces": [
"application/json"
@ -2432,16 +2300,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
},
@ -2473,7 +2334,7 @@ const docTemplate = `{
}
}
},
"/repos/{owner}/{name}/registry": {
"/repos/{repo_id}/registry": {
"get": {
"produces": [
"application/json"
@ -2492,16 +2353,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
},
@ -2550,16 +2404,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
},
@ -2583,7 +2430,7 @@ const docTemplate = `{
}
}
},
"/repos/{owner}/{name}/registry/{registry}": {
"/repos/{repo_id}/registry/{registry}": {
"get": {
"produces": [
"application/json"
@ -2602,16 +2449,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
},
@ -2650,16 +2490,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
},
@ -2695,16 +2528,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
},
@ -2735,7 +2561,7 @@ const docTemplate = `{
}
}
},
"/repos/{owner}/{name}/repair": {
"/repos/{repo_id}/repair": {
"post": {
"produces": [
"text/plain"
@ -2754,16 +2580,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
}
@ -2775,7 +2594,7 @@ const docTemplate = `{
}
}
},
"/repos/{owner}/{name}/secrets": {
"/repos/{repo_id}/secrets": {
"get": {
"produces": [
"application/json"
@ -2794,16 +2613,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
},
@ -2852,16 +2664,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
},
@ -2885,7 +2690,7 @@ const docTemplate = `{
}
}
},
"/repos/{owner}/{name}/secrets/{secretName}": {
"/repos/{repo_id}/secrets/{secretName}": {
"get": {
"produces": [
"application/json"
@ -2904,16 +2709,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
},
@ -2952,16 +2750,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
},
@ -2997,16 +2788,9 @@ const docTemplate = `{
"required": true
},
{
"type": "string",
"description": "the repository owner's name",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"type": "integer",
"description": "the repository id",
"name": "repo_id",
"in": "path",
"required": true
},
@ -3753,24 +3537,15 @@ const docTemplate = `{
"finished_at": {
"type": "integer"
},
"full_name": {
"type": "string"
},
"id": {
"type": "integer"
},
"message": {
"type": "string"
},
"name": {
"type": "string"
},
"number": {
"type": "integer"
},
"owner": {
"type": "string"
},
"ref": {
"type": "string"
},
@ -3780,6 +3555,9 @@ const docTemplate = `{
"remote": {
"type": "string"
},
"repo_id": {
"type": "integer"
},
"started_at": {
"type": "integer"
},
@ -4037,6 +3815,10 @@ const docTemplate = `{
"default_branch": {
"type": "string"
},
"forge_remote_id": {
"description": "ForgeRemoteID is the unique identifier for the repository on the forge.",
"type": "string"
},
"full_name": {
"type": "string"
},

View file

@ -5,14 +5,14 @@ Woodpecker has integrated support for repository status badges. These badges can
## Badge endpoint
```text
<scheme>://<hostname>/api/badges/<owner>/<repo>/status.svg
<scheme>://<hostname>/api/badges/<repo-id>/status.svg
```
The status badge displays the status for the latest build to your default branch (e.g. master). You can customize the branch by adding the `branch` query parameter.
```diff
-<scheme>://<hostname>/api/badges/<owner>/<repo>/status.svg
+<scheme>://<hostname>/api/badges/<owner>/<repo>/status.svg?branch=<branch>
-<scheme>://<hostname>/api/badges/<repo-id>/status.svg
+<scheme>://<hostname>/api/badges/<repo-id>/status.svg?branch=<branch>
```
Please note status badges do not include pull request results, since the status of a pull request does not provide an accurate representation of your repository state.

View file

@ -19,7 +19,13 @@ Some versions need some changes to the server configuration or the pipeline conf
- Dropped support for [Coding](https://coding.net/) and [Gogs](https://gogs.io).
- `/api/queue/resume` & `/api/queue/pause` endpoint methods were changed from `GET` to `POST`
- rename `pipeline:` key in your workflow config to `steps:`
- If you want to migrate old logs to the new format, watch the error messages on start. If there are none we are good to go, else you have to plan a migration that can take hours. Set `WOODPECKER_ALLOW_LONG_MIGRATION` to true and let it run.
- If you want to migrate old logs to the new format, watch the error messages on start. If there are none we are good to go, else you have to plan a migration that can take hours. Set `WOODPECKER_MIGRATIONS_ALLOW_LONG` to true and let it run.
- Using `repo-id` in favor of `owner/repo` combination
- :warning: The api endpoints `/api/repos/{owner}/{repo}/...` were replaced by new endpoints using the repos id `/api/repos/{repo-id}`
- To find the id of a repo use the `/api/repos/lookup/{repo-full-name-with-slashes}` endpoint.
- The existing badge endpoint `/api/badges/{owner}/{repo}` will still work, but whenever possible try to use the new endpoint using the `repo-id`: `/api/badges/{repo-id}`.
- The UI urls for a repository changed from `/repos/{owner}/{repo}/...` to `/repos/{repo-id}/...`. You will be redirected automatically when using the old url.
- The woodpecker-go api-client is now using the `repo-id` instead of `owner/repo` for all functions
## 0.15.0

View file

@ -22,6 +22,7 @@ import (
"errors"
"fmt"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
@ -37,15 +38,29 @@ import (
// GetBadge
//
// @Summary Get status badge, SVG format
// @Router /badges/{owner}/{name}/status.svg [get]
// @Router /badges/{repo_id}/status.svg [get]
// @Produce image/svg+xml
// @Success 200
// @Tags Badges
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
func GetBadge(c *gin.Context) {
_store := store.FromContext(c)
repo, err := _store.GetRepoName(c.Param("owner") + "/" + c.Param("name"))
var repo *model.Repo
var err error
if c.Param("repo_name") != "" {
repo, err = _store.GetRepoName(c.Param("repo_id_or_owner") + "/" + c.Param("repo_name"))
} else {
var repoID int64
repoID, err = strconv.ParseInt(c.Param("repo_id_or_owner"), 10, 64)
if err != nil {
c.AbortWithStatus(http.StatusBadRequest)
return
}
repo, err = _store.GetRepo(repoID)
}
if err != nil || !repo.IsActive {
if err == nil || errors.Is(err, types.RecordNotExist) {
c.AbortWithStatus(http.StatusNotFound)
@ -80,7 +95,7 @@ func GetBadge(c *gin.Context) {
// @Description CCMenu displays the pipeline status of projects on a CI server as an item in the Mac's menu bar.
// @Description More details on how to install, you can find at http://ccmenu.org/
// @Description The response format adheres to CCTray v1 Specification, https://cctray.org/v1/
// @Router /badges/{owner}/{name}/cc.xml [get]
// @Router /badges/{repo_id}/cc.xml [get]
// @Produce xml
// @Success 200
// @Tags Badges

View file

@ -31,13 +31,12 @@ import (
// GetCron
//
// @Summary Get a cron job by id
// @Router /repos/{owner}/{name}/cron/{cron} [get]
// @Router /repos/{repo_id}/cron/{cron} [get]
// @Produce json
// @Success 200 {object} Cron
// @Tags Repository cron jobs
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
// @Param cron path string true "the cron job id"
func GetCron(c *gin.Context) {
repo := session.Repo(c)
@ -58,13 +57,12 @@ func GetCron(c *gin.Context) {
// RunCron
//
// @Summary Start a cron job now
// @Router /repos/{owner}/{name}/cron/{cron} [post]
// @Router /repos/{repo_id}/cron/{cron} [post]
// @Produce json
// @Success 200 {object} Pipeline
// @Tags Repository cron jobs
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
// @Param cron path string true "the cron job id"
func RunCron(c *gin.Context) {
repo := session.Repo(c)
@ -99,13 +97,12 @@ func RunCron(c *gin.Context) {
// PostCron
//
// @Summary Persist/creat a cron job
// @Router /repos/{owner}/{name}/cron [post]
// @Router /repos/{repo_id}/cron [post]
// @Produce json
// @Success 200 {object} Cron
// @Tags Repository cron jobs
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
// @Param cronJob body Cron true "the new cron job"
func PostCron(c *gin.Context) {
repo := session.Repo(c)
@ -156,13 +153,12 @@ func PostCron(c *gin.Context) {
// PatchCron
//
// @Summary Update a cron job
// @Router /repos/{owner}/{name}/cron/{cron} [patch]
// @Router /repos/{repo_id}/cron/{cron} [patch]
// @Produce json
// @Success 200 {object} Cron
// @Tags Repository cron jobs
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
// @Param cron path string true "the cron job id"
// @Param cronJob body Cron true "the cron job data"
func PatchCron(c *gin.Context) {
@ -226,13 +222,12 @@ func PatchCron(c *gin.Context) {
// GetCronList
//
// @Summary Get the cron job list
// @Router /repos/{owner}/{name}/cron [get]
// @Router /repos/{repo_id}/cron [get]
// @Produce json
// @Success 200 {array} Cron
// @Tags Repository cron jobs
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
// @Param page query int false "for response pagination, page offset number" default(1)
// @Param perPage query int false "for response pagination, max items per page" default(50)
func GetCronList(c *gin.Context) {
@ -248,13 +243,12 @@ func GetCronList(c *gin.Context) {
// DeleteCron
//
// @Summary Delete a cron job by id
// @Router /repos/{owner}/{name}/cron/{cron} [delete]
// @Router /repos/{repo_id}/cron/{cron} [delete]
// @Produce plain
// @Success 200
// @Tags Repository cron jobs
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
// @Param cron path string true "the cron job id"
func DeleteCron(c *gin.Context) {
repo := session.Repo(c)

View file

@ -39,13 +39,12 @@ import (
// CreatePipeline
//
// @Summary Run/trigger a pipelines
// @Router /repos/{owner}/{name}/pipelines [post]
// @Router /repos/{repo_id}/pipelines [post]
// @Produce json
// @Success 200 {object} Pipeline
// @Tags Pipelines
// @Tags Pipelines
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
// @Param options body PipelineOptions true "the options for the pipeline to run"
func CreatePipeline(c *gin.Context) {
_store := store.FromContext(c)
@ -97,15 +96,14 @@ func createTmpPipeline(event model.WebhookEvent, commitSHA string, repo *model.R
// GetPipelines
//
// @Summary Get pipelines, current running and past ones
// @Router /repos/{owner}/{name}/pipelines [get]
// @Router /repos/{repo_id}/pipelines [get]
// @Produce json
// @Success 200 {array} Pipeline
// @Tags Pipelines
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
// @Param page query int false "for response pagination, page offset number" default(1)
// @Param perPage query int false "for response pagination, max items per page" default(50)
// @Param perPage query int false "for response pagination, max items per page" default(50)
func GetPipelines(c *gin.Context) {
repo := session.Repo(c)
@ -124,14 +122,13 @@ func GetPipelines(c *gin.Context) {
// GetPipeline
//
// @Summary Pipeline information by number
// @Router /repos/{owner}/{name}/pipelines/{number} [get]
// @Router /repos/{repo_id}/pipelines/{number} [get]
// @Produce json
// @Success 200 {object} Pipeline
// @Tags Pipelines
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param number path int true "the number of the pipeline, OR 'latest'"
// @Param repo_id path int true "the repository id"
// @Param number path int true "the number of the pipeline, OR 'latest'"
func GetPipeline(c *gin.Context) {
_store := store.FromContext(c)
if c.Param("number") == "latest" {
@ -190,13 +187,12 @@ func GetPipelineLast(c *gin.Context) {
// GetStepLogs
//
// @Summary Log information
// @Router /repos/{owner}/{name}/logs/{number}/{stepID} [get]
// @Router /repos/{repo_id}/logs/{number}/{stepID} [get]
// @Produce json
// @Success 200 {array} LogEntry
// @Tags Pipeline logs
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
// @Param number path int true "the number of the pipeline"
// @Param stepID path int true "the step id"
func GetStepLogs(c *gin.Context) {
@ -247,13 +243,12 @@ func GetStepLogs(c *gin.Context) {
// GetPipelineConfig
//
// @Summary Pipeline configuration
// @Router /repos/{owner}/{name}/pipelines/{number}/config [get]
// @Router /repos/{repo_id}/pipelines/{number}/config [get]
// @Produce json
// @Success 200 {array} Config
// @Tags Pipelines
// @Tags Pipelines
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
// @Param number path int true "the number of the pipeline"
func GetPipelineConfig(c *gin.Context) {
_store := store.FromContext(c)
@ -282,13 +277,12 @@ func GetPipelineConfig(c *gin.Context) {
// CancelPipeline
//
// @Summary Cancels a pipeline
// @Router /repos/{owner}/{name}/pipelines/{number}/cancel [post]
// @Router /repos/{repo_id}/pipelines/{number}/cancel [post]
// @Produce plain
// @Success 200
// @Tags Pipelines
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
// @Param number path int true "the number of the pipeline"
func CancelPipeline(c *gin.Context) {
_store := store.FromContext(c)
@ -312,13 +306,12 @@ func CancelPipeline(c *gin.Context) {
// PostApproval
//
// @Summary Start pipelines in gated repos
// @Router /repos/{owner}/{name}/pipelines/{number}/approve [post]
// @Router /repos/{repo_id}/pipelines/{number}/approve [post]
// @Produce json
// @Success 200 {object} Pipeline
// @Tags Pipelines
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
// @Param number path int true "the number of the pipeline"
func PostApproval(c *gin.Context) {
var (
@ -345,13 +338,12 @@ func PostApproval(c *gin.Context) {
// PostDecline
//
// @Summary Decline pipelines in gated repos
// @Router /repos/{owner}/{name}/pipelines/{number}/decline [post]
// @Router /repos/{repo_id}/pipelines/{number}/decline [post]
// @Produce json
// @Success 200 {object} Pipeline
// @Tags Pipelines
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
// @Param number path int true "the number of the pipeline"
func PostDecline(c *gin.Context) {
var (
@ -396,7 +388,7 @@ func GetPipelineQueue(c *gin.Context) {
//
// @Summary Restart a pipeline
// @Description Restarts a pipeline optional with altered event, deploy or environment
// @Router /repos/{owner}/{name}/pipelines/{number} [post]
// @Router /repos/{repo_id}/pipelines/{number} [post]
// @Produce json
// @Success 200 {object} Pipeline
// @Tags Pipelines
@ -478,13 +470,12 @@ func PostPipeline(c *gin.Context) {
// DeletePipelineLogs
//
// @Summary Deletes log
// @Router /repos/{owner}/{name}/logs/{number} [post]
// @Router /repos/{repo_id}/logs/{number} [post]
// @Produce plain
// @Success 200
// @Tags Pipeline logs
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
// @Param number path int true "the number of the pipeline"
func DeletePipelineLogs(c *gin.Context) {
_store := store.FromContext(c)

View file

@ -27,13 +27,12 @@ import (
// GetRegistry
//
// @Summary Get a named registry
// @Router /repos/{owner}/{name}/registry/{registry} [get]
// @Router /repos/{repo_id}/registry/{registry} [get]
// @Produce json
// @Success 200 {object} Registry
// @Tags Repository registries
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
// @Param registry path string true "the registry name"
func GetRegistry(c *gin.Context) {
var (
@ -51,13 +50,12 @@ func GetRegistry(c *gin.Context) {
// PostRegistry
//
// @Summary Persist/create a registry
// @Router /repos/{owner}/{name}/registry [post]
// @Router /repos/{repo_id}/registry [post]
// @Produce json
// @Success 200 {object} Registry
// @Tags Repository registries
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
// @Param registry body Registry true "the new registry data"
func PostRegistry(c *gin.Context) {
repo := session.Repo(c)
@ -89,13 +87,12 @@ func PostRegistry(c *gin.Context) {
// PatchRegistry
//
// @Summary Update a named registry
// @Router /repos/{owner}/{name}/registry/{registry} [patch]
// @Router /repos/{repo_id}/registry/{registry} [patch]
// @Produce json
// @Success 200 {object} Registry
// @Tags Repository registries
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
// @Param registry path string true "the registry name"
// @Param registryData body Registry true "the attributes for the registry"
func PatchRegistry(c *gin.Context) {
@ -143,13 +140,12 @@ func PatchRegistry(c *gin.Context) {
// GetRegistryList
//
// @Summary Get the registry list
// @Router /repos/{owner}/{name}/registry [get]
// @Router /repos/{repo_id}/registry [get]
// @Produce json
// @Success 200 {array} Registry
// @Tags Repository registries
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
// @Param page query int false "for response pagination, page offset number" default(1)
// @Param perPage query int false "for response pagination, max items per page" default(50)
func GetRegistryList(c *gin.Context) {
@ -170,13 +166,12 @@ func GetRegistryList(c *gin.Context) {
// DeleteRegistry
//
// @Summary Delete a named registry
// @Router /repos/{owner}/{name}/registry/{registry} [delete]
// @Router /repos/{repo_id}/registry/{registry} [delete]
// @Produce plain
// @Success 200
// @Tags Repository registries
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
// @Param registry path string true "the registry name"
func DeleteRegistry(c *gin.Context) {
var (

View file

@ -21,6 +21,7 @@ import (
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
@ -38,21 +39,19 @@ import (
// PostRepo
//
// @Summary Activate a repository
// @Router /repos/{owner}/{name} [post]
// @Router /repos/{repo_id} [post]
// @Produce json
// @Success 200 {object} Repo
// @Tags Repositories
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
func PostRepo(c *gin.Context) {
forge := server.Config.Services.Forge
_store := store.FromContext(c)
user := session.User(c)
owner := c.Param("owner")
name := c.Param("name")
repo, err := _store.GetRepoName(owner + "/" + name)
forgeRemoteID := model.ForgeRemoteID(c.Query("forge_remote_id"))
repo, err := _store.GetRepoForgeID(forgeRemoteID)
enabledOnce := err == nil // if there's no error, the repo was found and enabled once already
if enabledOnce && repo.IsActive {
c.String(http.StatusConflict, "Repository is already active.")
@ -62,7 +61,7 @@ func PostRepo(c *gin.Context) {
return
}
from, err := forge.Repo(c, user, "0", owner, name)
from, err := forge.Repo(c, user, forgeRemoteID, "", "")
if err != nil {
c.String(http.StatusInternalServerError, "Could not fetch repository from forge.")
return
@ -147,13 +146,12 @@ func PostRepo(c *gin.Context) {
// PatchRepo
//
// @Summary Change a repository
// @Router /repos/{owner}/{name} [patch]
// @Router /repos/{repo_id} [patch]
// @Produce json
// @Success 200 {object} Repo
// @Tags Repositories
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
// @Param repo body RepoPatch true "the repository's information"
func PatchRepo(c *gin.Context) {
_store := store.FromContext(c)
@ -218,13 +216,12 @@ func PatchRepo(c *gin.Context) {
// ChownRepo
//
// @Summary Change a repository's owner, to the one holding the access token
// @Router /repos/{owner}/{name}/chown [post]
// @Router /repos/{repo_id}/chown [post]
// @Produce json
// @Success 200 {object} Repo
// @Tags Repositories
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
func ChownRepo(c *gin.Context) {
_store := store.FromContext(c)
repo := session.Repo(c)
@ -239,16 +236,42 @@ func ChownRepo(c *gin.Context) {
c.JSON(http.StatusOK, repo)
}
// GetRepo
// LookupRepo
//
// @Summary Get repository information
// @Router /repos/{owner}/{name} [get]
// @Summary Get repository by full-name
// @Router /repos/lookup/{repo_full_name} [get]
// @Produce json
// @Success 200 {object} Repo
// @Tags Repositories
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_full_name path string true "the repository full-name / slug"
func LookupRepo(c *gin.Context) {
_store := store.FromContext(c)
repoFullName := strings.TrimLeft(c.Param("repo_full_name"), "/")
repo, err := _store.GetRepoName(repoFullName)
if err != nil {
if errors.Is(err, types.RecordNotExist) {
c.AbortWithStatus(http.StatusNotFound)
return
}
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
c.JSON(http.StatusOK, repo)
}
// GetRepo
//
// @Summary Get repository information
// @Router /repos/{repo_id} [get]
// @Produce json
// @Success 200 {object} Repo
// @Tags Repositories
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param repo_id path int true "the repository id"
func GetRepo(c *gin.Context) {
c.JSON(http.StatusOK, session.Repo(c))
}
@ -257,7 +280,7 @@ func GetRepo(c *gin.Context) {
//
// @Summary Repository permission information
// @Description The repository permission, according to the used access token.
// @Router /repos/{owner}/{name}/permissions [get]
// @Router /repos/{repo_id}/permissions [get]
// @Produce json
// @Success 200 {object} Perm
// @Tags Repositories
@ -272,13 +295,12 @@ func GetRepoPermissions(c *gin.Context) {
// GetRepoBranches
//
// @Summary Get repository branches
// @Router /repos/{owner}/{name}/branches [get]
// @Router /repos/{repo_id}/branches [get]
// @Produce json
// @Success 200 {array} string
// @Tags Repositories
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
// @Param page query int false "for response pagination, page offset number" default(1)
// @Param perPage query int false "for response pagination, max items per page" default(50)
func GetRepoBranches(c *gin.Context) {
@ -298,13 +320,12 @@ func GetRepoBranches(c *gin.Context) {
// GetRepoPullRequests
//
// @Summary List active pull requests
// @Router /repos/{owner}/{name}/pull_requests [get]
// @Router /repos/{repo_id}/pull_requests [get]
// @Produce json
// @Success 200 {array} PullRequest
// @Tags Repositories
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
// @Param page query int false "for response pagination, page offset number" default(1)
// @Param perPage query int false "for response pagination, max items per page" default(50)
func GetRepoPullRequests(c *gin.Context) {
@ -324,13 +345,12 @@ func GetRepoPullRequests(c *gin.Context) {
// DeleteRepo
//
// @Summary Delete a repository
// @Router /repos/{owner}/{name} [delete]
// @Router /repos/{repo_id} [delete]
// @Produce json
// @Success 200 {object} Repo
// @Tags Repositories
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
func DeleteRepo(c *gin.Context) {
remove, _ := strconv.ParseBool(c.Query("remove"))
_store := store.FromContext(c)
@ -363,13 +383,12 @@ func DeleteRepo(c *gin.Context) {
// RepairRepo
//
// @Summary Repair a repository
// @Router /repos/{owner}/{name}/repair [post]
// @Router /repos/{repo_id}/repair [post]
// @Produce plain
// @Success 200
// @Tags Repositories
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
func RepairRepo(c *gin.Context) {
forge := server.Config.Services.Forge
_store := store.FromContext(c)
@ -435,13 +454,12 @@ func RepairRepo(c *gin.Context) {
// MoveRepo
//
// @Summary Move a repository to a new owner
// @Router /repos/{owner}/{name}/move [post]
// @Router /repos/{repo_id}/move [post]
// @Produce plain
// @Success 200
// @Tags Repositories
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
// @Param to query string true "the username to move the repository to"
func MoveRepo(c *gin.Context) {
forge := server.Config.Services.Forge

View file

@ -28,13 +28,12 @@ import (
// GetSecret
//
// @Summary Get a named secret
// @Router /repos/{owner}/{name}/secrets/{secretName} [get]
// @Router /repos/{repo_id}/secrets/{secretName} [get]
// @Produce json
// @Success 200 {object} Secret
// @Tags Repository secrets
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
// @Param secretName path string true "the secret name"
func GetSecret(c *gin.Context) {
var (
@ -52,13 +51,12 @@ func GetSecret(c *gin.Context) {
// PostSecret
//
// @Summary Persist/create a secret
// @Router /repos/{owner}/{name}/secrets [post]
// @Router /repos/{repo_id}/secrets [post]
// @Produce json
// @Success 200 {object} Secret
// @Tags Repository secrets
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
// @Param secret body Secret true "the new secret"
func PostSecret(c *gin.Context) {
repo := session.Repo(c)
@ -90,13 +88,12 @@ func PostSecret(c *gin.Context) {
// PatchSecret
//
// @Summary Update a named secret
// @Router /repos/{owner}/{name}/secrets/{secretName} [patch]
// @Router /repos/{repo_id}/secrets/{secretName} [patch]
// @Produce json
// @Success 200 {object} Secret
// @Tags Repository secrets
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
// @Param secretName path string true "the secret name"
// @Param secret body Secret true "the secret itself"
func PatchSecret(c *gin.Context) {
@ -142,13 +139,12 @@ func PatchSecret(c *gin.Context) {
// GetSecretList
//
// @Summary Get the secret list
// @Router /repos/{owner}/{name}/secrets [get]
// @Router /repos/{repo_id}/secrets [get]
// @Produce json
// @Success 200 {array} Secret
// @Tags Repository secrets
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
// @Param page query int false "for response pagination, page offset number" default(1)
// @Param perPage query int false "for response pagination, max items per page" default(50)
func GetSecretList(c *gin.Context) {
@ -169,13 +165,12 @@ func GetSecretList(c *gin.Context) {
// DeleteSecret
//
// @Summary Delete a named secret
// @Router /repos/{owner}/{name}/secrets/{secretName} [delete]
// @Router /repos/{repo_id}/secrets/{secretName} [delete]
// @Produce plain
// @Success 200
// @Tags Repository secrets
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
// @Param secretName path string true "the secret name"
func DeleteSecret(c *gin.Context) {
var (

View file

@ -124,12 +124,11 @@ func EventStreamSSE(c *gin.Context) {
// LogStream
//
// @Summary Log stream
// @Router /logs/{owner}/{name}/{pipeline}/{stepID} [get]
// @Router /logs/{repo_id}/{pipeline}/{stepID} [get]
// @Produce plain
// @Success 200
// @Tags Pipeline logs
// @Param owner path string true "the repository owner's name"
// @Param name path string true "the repository name"
// @Param repo_id path int true "the repository id"
// @Param pipeline path int true "the number of the pipeline"
// @Param stepID path int true "the step id"
func LogStreamSSE(c *gin.Context) {

View file

@ -96,9 +96,9 @@ func GetRepos(c *gin.Context) {
}
if all {
active := map[string]bool{}
active := map[model.ForgeRemoteID]*model.Repo{}
for _, r := range activeRepos {
active[r.FullName] = r.IsActive
active[r.ForgeRemoteID] = r
}
_repos, err := _forge.Repos(c, user)
@ -106,13 +106,18 @@ func GetRepos(c *gin.Context) {
c.String(http.StatusInternalServerError, "Error fetching repository list. %s", err)
return
}
var repos []*model.Repo
for _, r := range _repos {
if r.Perm.Push {
if active[r.FullName] {
r.IsActive = true
if active[r.ForgeRemoteID] != nil && active[r.ForgeRemoteID].IsActive {
existingRepo := active[r.ForgeRemoteID]
existingRepo.Update(r)
existingRepo.IsActive = true
repos = append(repos, existingRepo)
} else {
repos = append(repos, r)
}
repos = append(repos, r)
}
}

View file

@ -74,8 +74,8 @@ func GetPipelineStatusDescription(status model.StatusValue) string {
func GetPipelineStatusLink(repo *model.Repo, pipeline *model.Pipeline, step *model.Step) string {
if step == nil {
return fmt.Sprintf("%s/%s/pipeline/%d", server.Config.Server.Host, repo.FullName, pipeline.Number)
return fmt.Sprintf("%s/repos/%d/pipeline/%d", server.Config.Server.Host, repo.ID, pipeline.Number)
}
return fmt.Sprintf("%s/%s/pipeline/%d/%d", server.Config.Server.Host, repo.FullName, pipeline.Number, step.PID)
return fmt.Sprintf("%s/repos/%d/pipeline/%d/%d", server.Config.Server.Host, repo.ID, pipeline.Number, step.PID)
}

View file

@ -290,12 +290,6 @@ func (g *GitLab) Repos(ctx context.Context, user *model.User) ([]*model.Repo, er
return nil, err
}
// TODO(648) remove when woodpecker understands nested repos
if strings.Count(repo.FullName, "/") > 1 {
log.Debug().Msgf("Skipping nested repository %s for user %s, because they are not supported, yet (see #648).", repo.FullName, user.Login)
continue
}
repos = append(repos, repo)
}

View file

@ -17,10 +17,7 @@ package model
// Feed represents an item in the user's feed or timeline.
type Feed struct {
Owner string `json:"owner" xorm:"feed_repo_owner"`
Name string `json:"name" xorm:"feed_repo_name"`
FullName string `json:"full_name" xorm:"feed_repo_full_name"`
RepoID int64 `json:"repo_id" xorm:"feed_repo_id"`
ID int64 `json:"id,omitempty" xorm:"feed_pipeline_id"`
Number int64 `json:"number,omitempty" xorm:"feed_pipeline_number"`
Event string `json:"event,omitempty" xorm:"feed_pipeline_event"`

View file

@ -25,7 +25,7 @@ type Repo struct {
ID int64 `json:"id,omitempty" xorm:"pk autoincr 'repo_id'"`
UserID int64 `json:"-" xorm:"repo_user_id"`
// ForgeRemoteID is the unique identifier for the repository on the forge.
ForgeRemoteID ForgeRemoteID `json:"-" xorm:"forge_remote_id"`
ForgeRemoteID ForgeRemoteID `json:"forge_remote_id" xorm:"forge_remote_id"`
Owner string `json:"owner" xorm:"UNIQUE(name) 'repo_owner'"`
Name string `json:"name" xorm:"UNIQUE(name) 'repo_name'"`
FullName string `json:"full_name" xorm:"UNIQUE 'repo_full_name'"`

View file

@ -61,8 +61,9 @@ func apiRoutes(e *gin.Engine) {
}
}
apiBase.POST("/repos/:owner/:name", session.MustUser(), api.PostRepo)
repoBase := apiBase.Group("/repos/:owner/:name")
apiBase.GET("/repos/lookup/*repo_full_name", api.LookupRepo) // TODO: check if this public route is a security issue
apiBase.POST("/repos", session.MustUser(), api.PostRepo)
repoBase := apiBase.Group("/repos/:repo_id")
{
repoBase.Use(session.SetRepo())
repoBase.Use(session.SetPerm())
@ -125,12 +126,18 @@ func apiRoutes(e *gin.Engine) {
}
}
badges := apiBase.Group("/badges/:owner/:name")
badges := apiBase.Group("/badges/:repo_id_or_owner")
{
badges.GET("/status.svg", api.GetBadge)
badges.GET("/cc.xml", api.GetCC)
}
_badges := apiBase.Group("/badges/:repo_id_or_owner/:repo_name")
{
_badges.GET("/status.svg", api.GetBadge)
_badges.GET("/cc.xml", api.GetCC)
}
pipelines := apiBase.Group("/pipelines")
{
pipelines.Use(session.MustAdmin())

View file

@ -17,6 +17,7 @@ package session
import (
"errors"
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
@ -44,14 +45,28 @@ func Repo(c *gin.Context) *model.Repo {
func SetRepo() gin.HandlerFunc {
return func(c *gin.Context) {
var (
_store = store.FromContext(c)
owner = c.Param("owner")
name = c.Param("name")
user = User(c)
_store = store.FromContext(c)
owner = c.Param("owner")
name = c.Param("name")
_repoID = c.Param("repo_id")
user = User(c)
)
repo, err := _store.GetRepoName(owner + "/" + name)
if err == nil {
var repo *model.Repo
var err error
if _repoID != "" {
var repoID int64
repoID, err = strconv.ParseInt(_repoID, 10, 64)
if err != nil {
c.AbortWithStatus(http.StatusBadRequest)
return
}
repo, err = _store.GetRepo(repoID)
} else {
repo, err = _store.GetRepoName(owner + "/" + name)
}
if repo != nil {
c.Set("repo", repo)
c.Next()
return
@ -64,15 +79,17 @@ func SetRepo() gin.HandlerFunc {
err.Error(),
)
if user != nil {
if errors.Is(err, types.RecordNotExist) {
c.AbortWithStatus(http.StatusNotFound)
return
}
_ = c.AbortWithError(http.StatusInternalServerError, err)
} else {
if user == nil {
c.AbortWithStatus(http.StatusUnauthorized)
return
}
if errors.Is(err, types.RecordNotExist) {
c.AbortWithStatus(http.StatusNotFound)
return
}
_ = c.AbortWithError(http.StatusInternalServerError, err)
}
}

View file

@ -20,9 +20,7 @@ import (
"github.com/woodpecker-ci/woodpecker/server/model"
)
var feedItemSelect = `repos.repo_owner as feed_repo_owner,
repos.repo_name as feed_repo_name,
repos.repo_full_name as feed_repo_full_name,
var feedItemSelect = `repos.repo_id as feed_repo_id,
pipelines.pipeline_id as feed_pipeline_id,
pipelines.pipeline_number as feed_pipeline_number,
pipelines.pipeline_event as feed_pipeline_event,

View file

@ -126,6 +126,7 @@ func TestRepoListLatest(t *testing.T) {
assert.NoError(t, store.CreateUser(user))
repo1 := &model.Repo{
ID: 1,
Owner: "bradrydzewski",
Name: "test",
FullName: "bradrydzewski/test",
@ -133,6 +134,7 @@ func TestRepoListLatest(t *testing.T) {
IsActive: true,
}
repo2 := &model.Repo{
ID: 2,
Owner: "test",
Name: "test",
FullName: "test/test",
@ -140,6 +142,7 @@ func TestRepoListLatest(t *testing.T) {
IsActive: true,
}
repo3 := &model.Repo{
ID: 3,
Owner: "octocat",
Name: "hello-world",
FullName: "octocat/hello-world",
@ -189,13 +192,13 @@ func TestRepoListLatest(t *testing.T) {
if got, want := pipelines[0].Status, string(model.StatusRunning); want != got {
t.Errorf("Want repository status %s, got %s", want, got)
}
if got, want := pipelines[0].FullName, repo1.FullName; want != got {
t.Errorf("Want repository name %s, got %s", want, got)
if got, want := pipelines[0].RepoID, repo1.ID; want != got {
t.Errorf("Want repository id %d, got %d", want, got)
}
if got, want := pipelines[1].Status, string(model.StatusKilled); want != got {
t.Errorf("Want repository status %s, got %s", want, got)
}
if got, want := pipelines[1].FullName, repo2.FullName; want != got {
t.Errorf("Want repository name %s, got %s", want, got)
if got, want := pipelines[1].RepoID, repo2.ID; want != got {
t.Errorf("Want repository id %d, got %d", want, got)
}
}

View file

@ -73,7 +73,7 @@ func (s storage) GetPipelineLastBefore(repo *model.Repo, branch string, num int6
}
func (s storage) GetPipelineList(repo *model.Repo, p *model.ListOptions) ([]*model.Pipeline, error) {
var pipelines []*model.Pipeline
pipelines := make([]*model.Pipeline, 0, 16)
return pipelines, s.paginate(p).Where("pipeline_repo_id = ?", repo.ID).
Desc("pipeline_number").
Find(&pipelines)

View file

@ -245,9 +245,9 @@ func TestUsers(t *testing.T) {
pipelines, err := store.UserFeed(user)
g.Assert(err).IsNil()
g.Assert(len(pipelines)).Equal(3)
g.Assert(pipelines[0].FullName).Equal(repo2.FullName)
g.Assert(pipelines[1].FullName).Equal(repo1.FullName)
g.Assert(pipelines[2].FullName).Equal(repo1.FullName)
g.Assert(pipelines[0].RepoID).Equal(repo2.ID)
g.Assert(pipelines[1].RepoID).Equal(repo1.ID)
g.Assert(pipelines[2].RepoID).Equal(repo1.ID)
})
})
}

View file

@ -99,12 +99,7 @@ function deleteVar(key: string) {
const pipelineNumber = toRef(props, 'pipelineNumber');
async function triggerDeployPipeline() {
loading.value = true;
const newPipeline = await apiClient.deployPipeline(
repo.value.owner,
repo.value.name,
pipelineNumber.value,
payload.value,
);
const newPipeline = await apiClient.deployPipeline(repo.value.id, pipelineNumber.value, payload.value);
emit('close');

View file

@ -80,7 +80,7 @@ const newPipelineVariable = ref<{ name: string; value: string }>({ name: '', val
const loading = ref(true);
onMounted(async () => {
const data = await usePaginate((page) => apiClient.getRepoBranches(repo.value.owner, repo.value.name, page));
const data = await usePaginate((page) => apiClient.getRepoBranches(repo.value.id, page));
branches.value = data.map((e) => ({
text: e,
value: e,
@ -103,7 +103,7 @@ function deleteVar(key: string) {
async function triggerManualPipeline() {
loading.value = true;
const pipeline = await apiClient.createPipeline(repo.value.owner, repo.value.name, payload.value);
const pipeline = await apiClient.createPipeline(repo.value.id, payload.value);
emit('close');

View file

@ -2,7 +2,7 @@
<div v-if="pipeline" class="flex text-color w-full">
<PipelineStatusIcon :status="pipeline.status" class="flex items-center" />
<div class="flex flex-col ml-4 min-w-0">
<span class="underline">{{ pipeline.owner }} / {{ pipeline.name }}</span>
<span class="underline">{{ repo?.owner }} / {{ repo?.name }}</span>
<span class="whitespace-nowrap overflow-hidden overflow-ellipsis">{{ message }}</span>
<div class="flex flex-col mt-2">
<div class="flex space-x-2 items-center">
@ -23,32 +23,24 @@
</div>
</template>
<script lang="ts">
<script lang="ts" setup>
import { Tooltip } from 'floating-vue';
import { defineComponent, PropType, toRef } from 'vue';
import { computed, toRef } from 'vue';
import Icon from '~/components/atomic/Icon.vue';
import PipelineStatusIcon from '~/components/repo/pipeline/PipelineStatusIcon.vue';
import usePipeline from '~/compositions/usePipeline';
import { PipelineFeed } from '~/lib/api/types';
import { useRepoStore } from '~/store/repos';
export default defineComponent({
name: 'PipelineFeedItem',
const props = defineProps<{
pipeline: PipelineFeed;
}>();
components: { PipelineStatusIcon, Icon, Tooltip },
const repoStore = useRepoStore();
props: {
pipeline: {
type: Object as PropType<PipelineFeed>,
required: true,
},
},
const pipeline = toRef(props, 'pipeline');
const repo = repoStore.getRepo(computed(() => pipeline.value.repo_id));
setup(props) {
const pipeline = toRef(props, 'pipeline');
const { since, duration, message, created } = usePipeline(pipeline);
return { since, duration, message, created };
},
});
const { since, duration, message, created } = usePipeline(pipeline);
</script>

View file

@ -1,43 +1,29 @@
<template>
<aside
v-if="isPipelineFeedOpen"
v-if="isOpen"
class="flex flex-col z-50 overflow-y-auto items-center bg-white dark:bg-dark-gray-800 dark:border-dark-500"
:aria-label="$t('pipeline_feed')"
>
<router-link
v-for="pipeline in sortedPipelineFeed"
v-for="pipeline in sortedPipelines"
:key="pipeline.id"
:to="{
name: 'repo-pipeline',
params: { repoOwner: pipeline.owner, repoName: pipeline.name, pipelineId: pipeline.number },
params: { repoId: pipeline.repo_id, pipelineId: pipeline.number },
}"
class="flex border-b py-4 px-2 w-full hover:bg-light-300 dark:hover:bg-dark-gray-900 dark:border-dark-gray-600 hover:shadow-sm"
>
<PipelineFeedItem :pipeline="pipeline" />
</router-link>
<span v-if="sortedPipelineFeed.length === 0" class="text-color m-4">{{ $t('repo.pipeline.no_pipelines') }}</span>
<span v-if="sortedPipelines.length === 0" class="text-color m-4">{{ $t('repo.pipeline.no_pipelines') }}</span>
</aside>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
<script lang="ts" setup>
import PipelineFeedItem from '~/components/pipeline-feed/PipelineFeedItem.vue';
import usePipelineFeed from '~/compositions/usePipelineFeed';
export default defineComponent({
name: 'PipelineFeedSidebar',
components: { PipelineFeedItem },
setup() {
const pipelineFeed = usePipelineFeed();
return {
isPipelineFeedOpen: pipelineFeed.isOpen,
sortedPipelineFeed: pipelineFeed.sortedPipelines,
};
},
});
const pipelineFeed = usePipelineFeed();
const { isOpen, sortedPipelines } = pipelineFeed;
</script>

View file

@ -5,7 +5,7 @@
:key="pipeline.id"
:to="{
name: 'repo-pipeline',
params: { repoOwner: repo.owner, repoName: repo.name, pipelineId: pipeline.number },
params: { pipelineId: pipeline.number },
}"
:pipeline="pipeline"
/>
@ -15,28 +15,12 @@
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue';
<script lang="ts" setup>
import Panel from '~/components/layout/Panel.vue';
import PipelineItem from '~/components/repo/pipeline/PipelineItem.vue';
import { Pipeline, Repo } from '~/lib/api/types';
import { Pipeline } from '~/lib/api/types';
export default defineComponent({
name: 'PipelineList',
components: { Panel, PipelineItem },
props: {
repo: {
type: Object as PropType<Repo>,
required: true,
},
pipelines: {
type: Object as PropType<Pipeline[] | undefined>,
required: true,
},
},
});
defineProps<{
pipelines: Pipeline[] | undefined;
}>();
</script>

View file

@ -190,7 +190,7 @@ async function download() {
let logs;
try {
downloadInProgress.value = true;
logs = await apiClient.getLogs(repo.value.owner, repo.value.name, pipeline.value.number, step.value.id);
logs = await apiClient.getLogs(repo.value.id, pipeline.value.number, step.value.id);
} catch (e) {
notifications.notifyError(e, i18n.t('repo.pipeline.log_download_error'));
return;
@ -239,22 +239,16 @@ async function loadLogs() {
}
if (isStepFinished(step.value)) {
const logs = await apiClient.getLogs(repo.value.owner, repo.value.name, pipeline.value.number, step.value.id);
const logs = await apiClient.getLogs(repo.value.id, pipeline.value.number, step.value.id);
logs?.forEach((line) => writeLog({ index: line.line, text: atob(line.data), time: line.time }));
flushLogs(false);
}
if (isStepRunning(step.value)) {
stream.value = apiClient.streamLogs(
repo.value.owner,
repo.value.name,
pipeline.value.number,
step.value.id,
(line) => {
writeLog({ index: line.line, text: atob(line.data), time: line.time });
flushLogs(true);
},
);
stream.value = apiClient.streamLogs(repo.value.id, pipeline.value.number, step.value.id, (line) => {
writeLog({ index: line.line, text: atob(line.data), time: line.time });
flushLogs(true);
});
}
}

View file

@ -45,8 +45,8 @@
</Panel>
</template>
<script lang="ts">
import { defineComponent, inject, Ref } from 'vue';
<script lang="ts" setup>
import { computed, inject, Ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
@ -57,74 +57,56 @@ import { useAsyncAction } from '~/compositions/useAsyncAction';
import useNotifications from '~/compositions/useNotifications';
import { Repo } from '~/lib/api/types';
export default defineComponent({
name: 'ActionsTab',
const apiClient = useApiClient();
const router = useRouter();
const notifications = useNotifications();
const i18n = useI18n();
components: { Button, Panel },
const repo = inject<Ref<Repo>>('repo');
setup() {
const apiClient = useApiClient();
const router = useRouter();
const notifications = useNotifications();
const i18n = useI18n();
const { doSubmit: repairRepo, isLoading: isRepairingRepo } = useAsyncAction(async () => {
if (!repo) {
throw new Error('Unexpected: Repo should be set');
}
const repo = inject<Ref<Repo>>('repo');
const { doSubmit: repairRepo, isLoading: isRepairingRepo } = useAsyncAction(async () => {
if (!repo) {
throw new Error('Unexpected: Repo should be set');
}
await apiClient.repairRepo(repo.value.owner, repo.value.name);
notifications.notify({ title: i18n.t('repo.settings.actions.repair.success'), type: 'success' });
});
const { doSubmit: deleteRepo, isLoading: isDeletingRepo } = useAsyncAction(async () => {
if (!repo) {
throw new Error('Unexpected: Repo should be set');
}
// TODO use proper dialog
// eslint-disable-next-line no-alert, no-restricted-globals
if (!confirm(i18n.t('repo.settings.actions.delete.confirm'))) {
return;
}
await apiClient.deleteRepo(repo.value.owner, repo.value.name);
notifications.notify({ title: i18n.t('repo.settings.actions.delete.success'), type: 'success' });
await router.replace({ name: 'repos' });
});
const { doSubmit: activateRepo, isLoading: isActivatingRepo } = useAsyncAction(async () => {
if (!repo) {
throw new Error('Unexpected: Repo should be set');
}
await apiClient.activateRepo(repo.value.owner, repo.value.name);
notifications.notify({ title: i18n.t('repo.settings.actions.enable.success'), type: 'success' });
});
const { doSubmit: deactivateRepo, isLoading: isDeactivatingRepo } = useAsyncAction(async () => {
if (!repo) {
throw new Error('Unexpected: Repo should be set');
}
await apiClient.deleteRepo(repo.value.owner, repo.value.name, false);
notifications.notify({ title: i18n.t('repo.settings.actions.disable.success'), type: 'success' });
await router.replace({ name: 'repos' });
});
return {
isActive: repo?.value.active,
isRepairingRepo,
isDeletingRepo,
isDeactivatingRepo,
isActivatingRepo,
deleteRepo,
repairRepo,
deactivateRepo,
activateRepo,
};
},
await apiClient.repairRepo(repo.value.id);
notifications.notify({ title: i18n.t('repo.settings.actions.repair.success'), type: 'success' });
});
const { doSubmit: deleteRepo, isLoading: isDeletingRepo } = useAsyncAction(async () => {
if (!repo) {
throw new Error('Unexpected: Repo should be set');
}
// TODO use proper dialog
// eslint-disable-next-line no-alert, no-restricted-globals
if (!confirm(i18n.t('repo.settings.actions.delete.confirm'))) {
return;
}
await apiClient.deleteRepo(repo.value.id);
notifications.notify({ title: i18n.t('repo.settings.actions.delete.success'), type: 'success' });
await router.replace({ name: 'repos' });
});
const { doSubmit: activateRepo, isLoading: isActivatingRepo } = useAsyncAction(async () => {
if (!repo) {
throw new Error('Unexpected: Repo should be set');
}
await apiClient.activateRepo(repo.value.forge_remote_id);
notifications.notify({ title: i18n.t('repo.settings.actions.enable.success'), type: 'success' });
});
const { doSubmit: deactivateRepo, isLoading: isDeactivatingRepo } = useAsyncAction(async () => {
if (!repo) {
throw new Error('Unexpected: Repo should be set');
}
await apiClient.deleteRepo(repo.value.id, false);
notifications.notify({ title: i18n.t('repo.settings.actions.disable.success'), type: 'success' });
await router.replace({ name: 'repos' });
});
const isActive = computed(() => repo?.value.active);
</script>

View file

@ -75,7 +75,7 @@ export default defineComponent({
throw new Error('Unexpected: "repo" should be provided at this place');
}
branches.value = (await usePaginate((page) => apiClient.getRepoBranches(repo.value.owner, repo.value.name, page)))
branches.value = (await usePaginate((page) => apiClient.getRepoBranches(repo.value.id, page)))
.map((b) => ({
value: b,
text: b,
@ -91,14 +91,9 @@ export default defineComponent({
window.location.port ? `:${window.location.port}` : ''
}`;
const badgeUrl = computed(
() =>
`/api/badges/${repo.value.owner}/${repo.value.name}/status.svg${
branch.value !== '' ? `?branch=${branch.value}` : ''
}`,
);
const repoUrl = computed(
() => `/${repo.value.owner}/${repo.value.name}${branch.value !== '' ? `/branches/${branch.value}` : ''}`,
() => `/api/badges/${repo.value.id}/status.svg${branch.value !== '' ? `?branch=${branch.value}` : ''}`,
);
const repoUrl = computed(() => `/${repo.value.id}${branch.value !== '' ? `/branches/${branch.value}` : ''}`);
const badgeContent = computed(() => {
if (!repo) {

View file

@ -121,7 +121,7 @@ async function loadCrons(page: number): Promise<Cron[] | null> {
throw new Error("Unexpected: Can't load repo");
}
return apiClient.getCronList(repo.value.owner, repo.value.name, page);
return apiClient.getCronList(repo.value.id, page);
}
const { resetPage, data: crons } = usePagination(loadCrons, () => !selectedCron.value);
@ -136,9 +136,9 @@ const { doSubmit: createCron, isLoading: isSaving } = useAsyncAction(async () =>
}
if (isEditingCron.value) {
await apiClient.updateCron(repo.value.owner, repo.value.name, selectedCron.value);
await apiClient.updateCron(repo.value.id, selectedCron.value);
} else {
await apiClient.createCron(repo.value.owner, repo.value.name, selectedCron.value);
await apiClient.createCron(repo.value.id, selectedCron.value);
}
notifications.notify({
title: i18n.t(isEditingCron.value ? 'repo.settings.crons.saved' : i18n.t('repo.settings.crons.created')),
@ -153,7 +153,7 @@ const { doSubmit: deleteCron, isLoading: isDeleting } = useAsyncAction(async (_c
throw new Error("Unexpected: Can't load repo");
}
await apiClient.deleteCron(repo.value.owner, repo.value.name, _cron.id);
await apiClient.deleteCron(repo.value.id, _cron.id);
notifications.notify({ title: i18n.t('repo.settings.crons.deleted'), type: 'success' });
resetPage();
});
@ -163,12 +163,10 @@ const { doSubmit: runCron } = useAsyncAction(async (_cron: Cron) => {
throw new Error("Unexpected: Can't load repo");
}
const pipeline = await apiClient.runCron(repo.value.owner, repo.value.name, _cron.id);
const pipeline = await apiClient.runCron(repo.value.id, _cron.id);
await router.push({
name: 'repo-pipeline',
params: {
repoOwner: repo.value.owner,
repoName: repo.value.name,
pipelineId: pipeline.number,
},
});

View file

@ -148,7 +148,7 @@ export default defineComponent({
throw new Error('Unexpected: Repo should be set');
}
await repoStore.loadRepo(repo.value.owner, repo.value.name);
await repoStore.loadRepo(repo.value.id);
loadRepoSettings();
}
@ -161,7 +161,7 @@ export default defineComponent({
throw new Error('Unexpected: Repo-Settings should be set');
}
await apiClient.updateRepo(repo.value.owner, repo.value.name, repoSettings.value);
await apiClient.updateRepo(repo.value.id, repoSettings.value);
await loadRepo();
notifications.notify({ title: i18n.t('repo.settings.general.success'), type: 'success' });
});

View file

@ -124,7 +124,7 @@ export default defineComponent({
throw new Error("Unexpected: Can't load repo");
}
return apiClient.getRegistryList(repo.value.owner, repo.value.name, page);
return apiClient.getRegistryList(repo.value.id, page);
}
const { resetPage, data: registries } = usePagination(loadRegistries, () => !selectedRegistry.value);
@ -139,9 +139,9 @@ export default defineComponent({
}
if (isEditingRegistry.value) {
await apiClient.updateRegistry(repo.value.owner, repo.value.name, selectedRegistry.value);
await apiClient.updateRegistry(repo.value.id, selectedRegistry.value);
} else {
await apiClient.createRegistry(repo.value.owner, repo.value.name, selectedRegistry.value);
await apiClient.createRegistry(repo.value.id, selectedRegistry.value);
}
notifications.notify({
title: i18n.t(
@ -159,7 +159,7 @@ export default defineComponent({
}
const registryAddress = encodeURIComponent(_registry.address);
await apiClient.deleteRegistry(repo.value.owner, repo.value.name, registryAddress);
await apiClient.deleteRegistry(repo.value.id, registryAddress);
notifications.notify({ title: i18n.t('repo.settings.registries.deleted'), type: 'success' });
resetPage();
});

View file

@ -86,7 +86,7 @@ export default defineComponent({
throw new Error("Unexpected: Can't load repo");
}
return apiClient.getSecretList(repo.value.owner, repo.value.name, page);
return apiClient.getSecretList(repo.value.id, page);
}
const { resetPage, data: secrets } = usePagination(loadSecrets, () => !selectedSecret.value);
@ -101,9 +101,9 @@ export default defineComponent({
}
if (isEditingSecret.value) {
await apiClient.updateSecret(repo.value.owner, repo.value.name, selectedSecret.value);
await apiClient.updateSecret(repo.value.id, selectedSecret.value);
} else {
await apiClient.createSecret(repo.value.owner, repo.value.name, selectedSecret.value);
await apiClient.createSecret(repo.value.id, selectedSecret.value);
}
notifications.notify({
title: i18n.t(isEditingSecret.value ? 'repo.settings.secrets.saved' : 'repo.settings.secrets.created'),
@ -118,7 +118,7 @@ export default defineComponent({
throw new Error("Unexpected: Can't load repo");
}
await apiClient.deleteSecret(repo.value.owner, repo.value.name, _secret.name);
await apiClient.deleteSecret(repo.value.id, _secret.name);
notifications.notify({ title: i18n.t('repo.settings.secrets.deleted'), type: 'success' });
resetPage();
});

View file

@ -28,13 +28,13 @@ export default () => {
return;
}
const { pipeline } = data;
pipelineStore.setPipeline(repo.owner, repo.name, pipeline);
pipelineStore.setPipeline(repo.id, pipeline);
// contains step update
if (!data.step) {
return;
}
const { step } = data;
pipelineStore.setStep(repo.owner, repo.name, pipeline.number, step);
pipelineStore.setStep(repo.id, pipeline.number, step);
});
};

View file

@ -3,12 +3,11 @@ import { RouteLocationRaw, useRouter } from 'vue-router';
export function useRouteBackOrDefault(to: RouteLocationRaw) {
const router = useRouter();
return () => {
// TODO: use history navigation once we have found a solution for filtering external history entries
// if (window.history.length > 2) {
// router.back();
// } else {
router.replace(to);
// }
return async () => {
if ((window.history.state as { back: string }).back === null) {
await router.replace(to);
return;
}
router.back();
};
}

View file

@ -40,66 +40,70 @@ export default class WoodpeckerClient extends ApiClient {
return this._get(`/api/user/repos?${query}`) as Promise<Repo[]>;
}
getRepo(owner: string, repo: string): Promise<Repo> {
return this._get(`/api/repos/${owner}/${repo}`) as Promise<Repo>;
lookupRepo(owner: string, name: string): Promise<Repo | undefined> {
return this._get(`/api/repos/lookup/${owner}/${name}`) as Promise<Repo | undefined>;
}
getRepoPermissions(owner: string, repo: string): Promise<RepoPermissions> {
return this._get(`/api/repos/${owner}/${repo}/permissions`) as Promise<RepoPermissions>;
getRepo(repoId: number): Promise<Repo> {
return this._get(`/api/repos/${repoId}`) as Promise<Repo>;
}
getRepoBranches(owner: string, repo: string, page: number): Promise<string[]> {
return this._get(`/api/repos/${owner}/${repo}/branches?page=${page}`) as Promise<string[]>;
getRepoPermissions(repoId: number): Promise<RepoPermissions> {
return this._get(`/api/repos/${repoId}/permissions`) as Promise<RepoPermissions>;
}
getRepoPullRequests(owner: string, repo: string, page: number): Promise<PullRequest[]> {
return this._get(`/api/repos/${owner}/${repo}/pull_requests?page=${page}`) as Promise<PullRequest[]>;
getRepoBranches(repoId: number, page: number): Promise<string[]> {
return this._get(`/api/repos/${repoId}/branches?page=${page}`) as Promise<string[]>;
}
activateRepo(owner: string, repo: string): Promise<unknown> {
return this._post(`/api/repos/${owner}/${repo}`);
getRepoPullRequests(repoId: number, page: number): Promise<PullRequest[]> {
return this._get(`/api/repos/${repoId}/pull_requests?page=${page}`) as Promise<PullRequest[]>;
}
updateRepo(owner: string, repo: string, repoSettings: RepoSettings): Promise<unknown> {
return this._patch(`/api/repos/${owner}/${repo}`, repoSettings);
activateRepo(forgeRemoteId: string): Promise<Repo> {
return this._post(`/api/repos?forge_remote_id=${forgeRemoteId}`) as Promise<Repo>;
}
deleteRepo(owner: string, repo: string, remove = true): Promise<unknown> {
updateRepo(repoId: number, repoSettings: RepoSettings): Promise<unknown> {
return this._patch(`/api/repos/${repoId}`, repoSettings);
}
deleteRepo(repoId: number, remove = true): Promise<unknown> {
const query = encodeQueryString({ remove });
return this._delete(`/api/repos/${owner}/${repo}?${query}`);
return this._delete(`/api/repos/${repoId}?${query}`);
}
repairRepo(owner: string, repo: string): Promise<unknown> {
return this._post(`/api/repos/${owner}/${repo}/repair`);
repairRepo(repoId: number): Promise<unknown> {
return this._post(`/api/repos/${repoId}/repair`);
}
createPipeline(owner: string, repo: string, options: PipelineOptions): Promise<Pipeline> {
return this._post(`/api/repos/${owner}/${repo}/pipelines`, options) as Promise<Pipeline>;
createPipeline(repoId: number, options: PipelineOptions): Promise<Pipeline> {
return this._post(`/api/repos/${repoId}/pipelines`, options) as Promise<Pipeline>;
}
// Deploy triggers a deployment for an existing pipeline using the
// specified target environment.
deployPipeline(owner: string, repo: string, pipelineNumber: string, options: DeploymentOptions): Promise<Pipeline> {
deployPipeline(repoId: number, pipelineNumber: string, options: DeploymentOptions): Promise<Pipeline> {
const vars = {
...options.variables,
event: 'deployment',
deploy_to: options.environment,
};
const query = encodeQueryString(vars);
return this._post(`/api/repos/${owner}/${repo}/pipelines/${pipelineNumber}?${query}`) as Promise<Pipeline>;
return this._post(`/api/repos/${repoId}/pipelines/${pipelineNumber}?${query}`) as Promise<Pipeline>;
}
getPipelineList(owner: string, repo: string, opts?: Record<string, string | number | boolean>): Promise<Pipeline[]> {
getPipelineList(repoId: number, opts?: Record<string, string | number | boolean>): Promise<Pipeline[]> {
const query = encodeQueryString(opts);
return this._get(`/api/repos/${owner}/${repo}/pipelines?${query}`) as Promise<Pipeline[]>;
return this._get(`/api/repos/${repoId}/pipelines?${query}`) as Promise<Pipeline[]>;
}
getPipeline(owner: string, repo: string, pipelineNumber: number | 'latest'): Promise<Pipeline> {
return this._get(`/api/repos/${owner}/${repo}/pipelines/${pipelineNumber}`) as Promise<Pipeline>;
getPipeline(repoId: number, pipelineNumber: number | 'latest'): Promise<Pipeline> {
return this._get(`/api/repos/${repoId}/pipelines/${pipelineNumber}`) as Promise<Pipeline>;
}
getPipelineConfig(owner: string, repo: string, pipelineNumber: number): Promise<PipelineConfig[]> {
return this._get(`/api/repos/${owner}/${repo}/pipelines/${pipelineNumber}/config`) as Promise<PipelineConfig[]>;
getPipelineConfig(repoId: number, pipelineNumber: number): Promise<PipelineConfig[]> {
return this._get(`/api/repos/${repoId}/pipelines/${pipelineNumber}/config`) as Promise<PipelineConfig[]>;
}
getPipelineFeed(opts?: Record<string, string | number | boolean>): Promise<PipelineFeed[]> {
@ -107,82 +111,81 @@ export default class WoodpeckerClient extends ApiClient {
return this._get(`/api/user/feed?${query}`) as Promise<PipelineFeed[]>;
}
cancelPipeline(owner: string, repo: string, pipelineNumber: number): Promise<unknown> {
return this._post(`/api/repos/${owner}/${repo}/pipelines/${pipelineNumber}/cancel`);
cancelPipeline(repoId: number, pipelineNumber: number): Promise<unknown> {
return this._post(`/api/repos/${repoId}/pipelines/${pipelineNumber}/cancel`);
}
approvePipeline(owner: string, repo: string, pipelineNumber: string): Promise<unknown> {
return this._post(`/api/repos/${owner}/${repo}/pipelines/${pipelineNumber}/approve`);
approvePipeline(repoId: number, pipelineNumber: string): Promise<unknown> {
return this._post(`/api/repos/${repoId}/pipelines/${pipelineNumber}/approve`);
}
declinePipeline(owner: string, repo: string, pipelineNumber: string): Promise<unknown> {
return this._post(`/api/repos/${owner}/${repo}/pipelines/${pipelineNumber}/decline`);
declinePipeline(repoId: number, pipelineNumber: string): Promise<unknown> {
return this._post(`/api/repos/${repoId}/pipelines/${pipelineNumber}/decline`);
}
restartPipeline(
owner: string,
repo: string,
repoId: number,
pipeline: string,
opts?: Record<string, string | number | boolean>,
): Promise<Pipeline> {
const query = encodeQueryString(opts);
return this._post(`/api/repos/${owner}/${repo}/pipelines/${pipeline}?${query}`) as Promise<Pipeline>;
return this._post(`/api/repos/${repoId}/pipelines/${pipeline}?${query}`) as Promise<Pipeline>;
}
getLogs(owner: string, repo: string, pipeline: number, stepId: number): Promise<PipelineLog[]> {
return this._get(`/api/repos/${owner}/${repo}/logs/${pipeline}/${stepId}`) as Promise<PipelineLog[]>;
getLogs(repoId: number, pipeline: number, step: number): Promise<PipelineLog[]> {
return this._get(`/api/repos/${repoId}/logs/${pipeline}/${step}`) as Promise<PipelineLog[]>;
}
getSecretList(owner: string, repo: string, page: number): Promise<Secret[] | null> {
return this._get(`/api/repos/${owner}/${repo}/secrets?page=${page}`) as Promise<Secret[] | null>;
getSecretList(repoId: number, page: number): Promise<Secret[] | null> {
return this._get(`/api/repos/${repoId}/secrets?page=${page}`) as Promise<Secret[] | null>;
}
createSecret(owner: string, repo: string, secret: Partial<Secret>): Promise<unknown> {
return this._post(`/api/repos/${owner}/${repo}/secrets`, secret);
createSecret(repoId: number, secret: Partial<Secret>): Promise<unknown> {
return this._post(`/api/repos/${repoId}/secrets`, secret);
}
updateSecret(owner: string, repo: string, secret: Partial<Secret>): Promise<unknown> {
return this._patch(`/api/repos/${owner}/${repo}/secrets/${secret.name}`, secret);
updateSecret(repoId: number, secret: Partial<Secret>): Promise<unknown> {
return this._patch(`/api/repos/${repoId}/secrets/${secret.name}`, secret);
}
deleteSecret(owner: string, repo: string, secretName: string): Promise<unknown> {
return this._delete(`/api/repos/${owner}/${repo}/secrets/${secretName}`);
deleteSecret(repoId: number, secretName: string): Promise<unknown> {
return this._delete(`/api/repos/${repoId}/secrets/${secretName}`);
}
getRegistryList(owner: string, repo: string, page: number): Promise<Registry[] | null> {
return this._get(`/api/repos/${owner}/${repo}/registry?page=${page}`) as Promise<Registry[] | null>;
getRegistryList(repoId: number, page: number): Promise<Registry[] | null> {
return this._get(`/api/repos/${repoId}/registry?page=${page}`) as Promise<Registry[] | null>;
}
createRegistry(owner: string, repo: string, registry: Partial<Registry>): Promise<unknown> {
return this._post(`/api/repos/${owner}/${repo}/registry`, registry);
createRegistry(repoId: number, registry: Partial<Registry>): Promise<unknown> {
return this._post(`/api/repos/${repoId}/registry`, registry);
}
updateRegistry(owner: string, repo: string, registry: Partial<Registry>): Promise<unknown> {
return this._patch(`/api/repos/${owner}/${repo}/registry/${registry.address}`, registry);
updateRegistry(repoId: number, registry: Partial<Registry>): Promise<unknown> {
return this._patch(`/api/repos/${repoId}/registry/${registry.address}`, registry);
}
deleteRegistry(owner: string, repo: string, registryAddress: string): Promise<unknown> {
return this._delete(`/api/repos/${owner}/${repo}/registry/${registryAddress}`);
deleteRegistry(repoId: number, registryAddress: string): Promise<unknown> {
return this._delete(`/api/repos/${repoId}/registry/${registryAddress}`);
}
getCronList(owner: string, repo: string, page: number): Promise<Cron[] | null> {
return this._get(`/api/repos/${owner}/${repo}/cron?page=${page}`) as Promise<Cron[] | null>;
getCronList(repoId: number, page: number): Promise<Cron[] | null> {
return this._get(`/api/repos/${repoId}/cron?page=${page}`) as Promise<Cron[] | null>;
}
createCron(owner: string, repo: string, cron: Partial<Cron>): Promise<unknown> {
return this._post(`/api/repos/${owner}/${repo}/cron`, cron);
createCron(repoId: number, cron: Partial<Cron>): Promise<unknown> {
return this._post(`/api/repos/${repoId}/cron`, cron);
}
updateCron(owner: string, repo: string, cron: Partial<Cron>): Promise<unknown> {
return this._patch(`/api/repos/${owner}/${repo}/cron/${cron.id}`, cron);
updateCron(repoId: number, cron: Partial<Cron>): Promise<unknown> {
return this._patch(`/api/repos/${repoId}/cron/${cron.id}`, cron);
}
deleteCron(owner: string, repo: string, cronId: number): Promise<unknown> {
return this._delete(`/api/repos/${owner}/${repo}/cron/${cronId}`);
deleteCron(repoId: number, cronId: number): Promise<unknown> {
return this._delete(`/api/repos/${repoId}/cron/${cronId}`);
}
runCron(owner: string, repo: string, cronId: number): Promise<Pipeline> {
return this._post(`/api/repos/${owner}/${repo}/cron/${cronId}`) as Promise<Pipeline>;
runCron(repoId: number, cronId: number): Promise<Pipeline> {
return this._post(`/api/repos/${repoId}/cron/${cronId}`) as Promise<Pipeline>;
}
getOrgPermissions(owner: string): Promise<OrgPermissions> {
@ -293,14 +296,13 @@ export default class WoodpeckerClient extends ApiClient {
}
streamLogs(
owner: string,
repo: string,
repoId: number,
pipeline: number,
step: number,
// eslint-disable-next-line promise/prefer-await-to-callbacks
callback: (data: PipelineLog) => void,
): EventSource {
return this._subscribe(`/api/stream/logs/${owner}/${repo}/${pipeline}/${step}`, callback, {
return this._subscribe(`/api/stream/logs/${repoId}/${pipeline}/${step}`, callback, {
reconnect: true,
});
}

View file

@ -127,7 +127,5 @@ export type PipelineLog = {
};
export type PipelineFeed = Pipeline & {
owner: string;
name: string;
full_name: string;
repo_id: number;
};

View file

@ -6,6 +6,9 @@ export type Repo = {
// The unique identifier for the repository.
id: number;
// The id of the repository on the source control management system.
forge_remote_id: string;
// The source control management being used.
// Currently this is either 'git' or 'hg' (Mercurial).
scm: string;
@ -57,6 +60,7 @@ export type Repo = {
// Events that will cancel running pipelines before starting a new one
cancel_previous_pipeline_events: string[];
netrc_only_trusted: boolean;
};

View file

@ -12,31 +12,108 @@ const routes: RouteRecordRaw[] = [
},
{
path: '/repos',
name: 'repos',
component: (): Component => import('~/views/Repos.vue'),
meta: { authentication: 'required' },
component: (): Component => import('~/views/RouterView.vue'),
children: [
{
path: '',
name: 'repos',
component: (): Component => import('~/views/Repos.vue'),
meta: { authentication: 'required' },
},
{
path: 'add',
name: 'repo-add',
component: (): Component => import('~/views/RepoAdd.vue'),
meta: { authentication: 'required' },
},
{
path: ':repoId',
name: 'repo-wrapper',
component: (): Component => import('~/views/repo/RepoWrapper.vue'),
props: true,
children: [
{
path: '',
name: 'repo',
component: (): Component => import('~/views/repo/RepoPipelines.vue'),
meta: { repoHeader: true },
},
{
path: 'branches',
name: 'repo-branches',
component: (): Component => import('~/views/repo/RepoBranches.vue'),
meta: { repoHeader: true },
},
{
path: 'branches/:branch',
name: 'repo-branch',
component: (): Component => import('~/views/repo/RepoBranch.vue'),
meta: { repoHeader: true },
props: (route) => ({ branch: route.params.branch }),
},
{
path: 'pull-requests',
name: 'repo-pull-requests',
component: (): Component => import('~/views/repo/RepoPullRequests.vue'),
meta: { repoHeader: true },
},
{
path: 'pull-requests/:pullRequest',
name: 'repo-pull-request',
component: (): Component => import('~/views/repo/RepoPullRequest.vue'),
meta: { repoHeader: true },
props: (route) => ({ pullRequest: route.params.pullRequest }),
},
{
path: 'pipeline/:pipelineId',
component: (): Component => import('~/views/repo/pipeline/PipelineWrapper.vue'),
props: true,
children: [
{
path: ':stepId?',
name: 'repo-pipeline',
component: (): Component => import('~/views/repo/pipeline/Pipeline.vue'),
props: true,
},
{
path: 'changed-files',
name: 'repo-pipeline-changed-files',
component: (): Component => import('~/views/repo/pipeline/PipelineChangedFiles.vue'),
},
{
path: 'config',
name: 'repo-pipeline-config',
component: (): Component => import('~/views/repo/pipeline/PipelineConfig.vue'),
props: true,
},
],
},
{
path: 'settings',
name: 'repo-settings',
component: (): Component => import('~/views/repo/RepoSettings.vue'),
meta: { authentication: 'required' },
props: true,
},
],
},
{
path: ':repoOwner/:repoName/:pathMatch(.*)*',
component: () => import('~/views/repo/RepoDeprecatedRedirect.vue'),
props: true,
},
],
},
{
path: '/repo/add',
name: 'repo-add',
component: (): Component => import('~/views/RepoAdd.vue'),
meta: { authentication: 'required' },
},
{
path: '/:repoOwner',
name: 'repos-owner',
component: (): Component => import('~/views/ReposOwner.vue'),
props: true,
},
{
path: '/org/:repoOwner',
path: '/org/:orgName',
component: (): Component => import('~/views/org/OrgWrapper.vue'),
props: true,
children: [
{
path: '',
name: 'org',
redirect: (route) => ({ name: 'repos-owner', params: route.params }),
component: (): Component => import('~/views/org/OrgRepos.vue'),
props: true,
},
{
path: 'settings',
@ -48,114 +125,25 @@ const routes: RouteRecordRaw[] = [
],
},
{
path: '/:repoOwner/:repoName',
name: 'repo-wrapper',
component: (): Component => import('~/views/repo/RepoWrapper.vue'),
props: true,
path: '/admin',
component: (): Component => import('~/views/RouterView.vue'),
meta: { authentication: 'required' },
children: [
{
path: '',
name: 'repo',
component: (): Component => import('~/views/repo/RepoPipelines.vue'),
meta: { repoHeader: true },
},
{
path: 'branches',
name: 'repo-branches',
component: (): Component => import('~/views/repo/RepoBranches.vue'),
meta: { repoHeader: true },
props: (route) => ({ branch: route.params.branch }),
},
{
path: 'branches/:branch',
name: 'repo-branch',
component: (): Component => import('~/views/repo/RepoBranch.vue'),
meta: { repoHeader: true },
props: (route) => ({ branch: route.params.branch }),
},
{
path: 'pull-requests',
name: 'repo-pull-requests',
component: (): Component => import('~/views/repo/RepoPullRequests.vue'),
meta: { repoHeader: true },
},
{
path: 'pull-requests/:pullRequest',
name: 'repo-pull-request',
component: (): Component => import('~/views/repo/RepoPullRequest.vue'),
meta: { repoHeader: true },
props: (route) => ({ pullRequest: route.params.pullRequest }),
},
{
path: 'pipeline/:pipelineId',
component: (): Component => import('~/views/repo/pipeline/PipelineWrapper.vue'),
name: 'admin',
component: (): Component => import('~/views/admin/Admin.vue'),
props: true,
children: [
{
path: ':stepId?',
name: 'repo-pipeline',
component: (): Component => import('~/views/repo/pipeline/Pipeline.vue'),
props: true,
},
{
path: 'changed-files',
name: 'repo-pipeline-changed-files',
component: (): Component => import('~/views/repo/pipeline/PipelineChangedFiles.vue'),
},
{
path: 'config',
name: 'repo-pipeline-config',
component: (): Component => import('~/views/repo/pipeline/PipelineConfig.vue'),
props: true,
},
],
},
{
path: 'settings',
name: 'repo-settings',
component: (): Component => import('~/views/repo/RepoSettings.vue'),
meta: { authentication: 'required' },
name: 'admin-settings',
component: (): Component => import('~/views/admin/AdminSettings.vue'),
props: true,
},
// TODO: redirect to support backwards compatibility => remove after some time
{
path: ':pipelineId',
redirect: (route) => ({ name: 'repo-pipeline', params: route.params }),
},
{
path: 'build/:pipelineId',
redirect: (route) => ({ name: 'repo-pipeline', params: route.params }),
children: [
{
path: ':procId?',
redirect: (route) => ({ name: 'repo-pipeline', params: route.params }),
},
{
path: 'changed-files',
redirect: (route) => ({ name: 'repo-pipeline-changed-files', params: route.params }),
},
{
path: 'config',
redirect: (route) => ({ name: 'repo-pipeline-config', params: route.params }),
},
],
},
],
},
{
path: '/admin',
name: 'admin',
component: (): Component => import('~/views/admin/Admin.vue'),
meta: { authentication: 'required' },
props: true,
},
{
path: '/admin/settings',
name: 'admin-settings',
component: (): Component => import('~/views/admin/AdminSettings.vue'),
meta: { authentication: 'required' },
props: true,
},
{
path: '/user',
name: 'user',
@ -177,6 +165,19 @@ const routes: RouteRecordRaw[] = [
meta: { blank: true },
props: true,
},
// TODO: deprecated routes => remove after some time
{
path: '/:ownerOrOrgId',
redirect: (route) => ({ name: 'org', params: route.params }),
},
{
path: '/:repoOwner/:repoName/:pathMatch(.*)*',
component: () => import('~/views/repo/RepoDeprecatedRedirect.vue'),
props: true,
},
// not found handler
{
path: '/:pathMatch(.*)*',
name: 'not-found',
@ -198,7 +199,8 @@ router.beforeEach(async (to, _, next) => {
}
const authentication = useAuthentication();
if (to.meta.authentication === 'required' && !authentication.isAuthenticated) {
const authenticationRequired = to.matched.some((record) => record.meta.authentication === 'required');
if (authenticationRequired && !authentication.isAuthenticated) {
next({ name: 'login', query: { url: to.fullPath } });
return;
}

View file

@ -4,41 +4,36 @@ import { computed, reactive, Ref, ref } from 'vue';
import useApiClient from '~/compositions/useApiClient';
import { Pipeline, PipelineFeed, PipelineStep } from '~/lib/api/types';
import { useRepoStore } from '~/store/repos';
import { comparePipelines, isPipelineActive, repoSlug } from '~/utils/helpers';
import { comparePipelines, isPipelineActive } from '~/utils/helpers';
export const usePipelineStore = defineStore('pipelines', () => {
const apiClient = useApiClient();
const repoStore = useRepoStore();
const pipelines: Map<string, Map<number, Pipeline>> = reactive(new Map());
const pipelines: Map<number, Map<number, Pipeline>> = reactive(new Map());
function setPipeline(owner: string, repo: string, pipeline: Pipeline) {
const _repoSlug = repoSlug(owner, repo);
const repoPipelines = pipelines.get(_repoSlug) || new Map();
function setPipeline(repoId: number, pipeline: Pipeline) {
const repoPipelines = pipelines.get(repoId) || new Map();
repoPipelines.set(pipeline.number, {
...(repoPipelines.get(pipeline.number) || {}),
...pipeline,
});
pipelines.set(_repoSlug, repoPipelines);
pipelines.set(repoId, repoPipelines);
}
function getRepoPipelines(owner: Ref<string>, repo: Ref<string>) {
return computed(() => {
const slug = repoSlug(owner.value, repo.value);
return Array.from(pipelines.get(slug)?.values() || []).sort(comparePipelines);
});
function getRepoPipelines(repoId: Ref<number>) {
return computed(() => Array.from(pipelines.get(repoId.value)?.values() || []).sort(comparePipelines));
}
function getPipeline(owner: Ref<string>, repo: Ref<string>, _pipelineNumber: Ref<string>) {
function getPipeline(repoId: Ref<number>, _pipelineNumber: Ref<string>) {
return computed(() => {
const slug = repoSlug(owner.value, repo.value);
const pipelineNumber = parseInt(_pipelineNumber.value, 10);
return pipelines.get(slug)?.get(pipelineNumber);
return pipelines.get(repoId.value)?.get(pipelineNumber);
});
}
function setStep(owner: string, repo: string, pipelineNumber: number, step: PipelineStep) {
const pipeline = getPipeline(ref(owner), ref(repo), ref(pipelineNumber.toString())).value;
function setStep(repoId: number, pipelineNumber: number, step: PipelineStep) {
const pipeline = getPipeline(ref(repoId), ref(pipelineNumber.toString())).value;
if (!pipeline) {
throw new Error("Can't find pipeline");
}
@ -48,38 +43,36 @@ export const usePipelineStore = defineStore('pipelines', () => {
}
pipeline.steps = [...pipeline.steps.filter((p) => p.pid !== step.pid), step];
setPipeline(owner, repo, pipeline);
setPipeline(repoId, pipeline);
}
async function loadRepoPipelines(owner: string, repo: string) {
const _pipelines = await apiClient.getPipelineList(owner, repo);
async function loadRepoPipelines(repoId: number) {
const _pipelines = await apiClient.getPipelineList(repoId);
_pipelines.forEach((pipeline) => {
setPipeline(owner, repo, pipeline);
setPipeline(repoId, pipeline);
});
}
async function loadPipeline(owner: string, repo: string, pipelinesNumber: number) {
const pipeline = await apiClient.getPipeline(owner, repo, pipelinesNumber);
setPipeline(owner, repo, pipeline);
async function loadPipeline(repoId: number, pipelinesNumber: number) {
const pipeline = await apiClient.getPipeline(repoId, pipelinesNumber);
setPipeline(repoId, pipeline);
}
const pipelineFeed = computed(() =>
Array.from(pipelines.entries())
.reduce<PipelineFeed[]>((acc, [_repoSlug, repoPipelines]) => {
.reduce<PipelineFeed[]>((acc, [_repoId, repoPipelines]) => {
const repoPipelinesArray = Array.from(repoPipelines.entries()).map(
([_pipelineNumber, pipeline]) =>
<PipelineFeed>{
...pipeline,
full_name: _repoSlug,
owner: _repoSlug.split('/')[0],
name: _repoSlug.split('/')[1],
repo_id: _repoId,
number: _pipelineNumber,
},
);
return [...acc, ...repoPipelinesArray];
}, [])
.sort(comparePipelines)
.filter((pipeline) => repoStore.ownedRepoSlugs.includes(pipeline.full_name)),
.filter((pipeline) => repoStore.ownedRepoIds.includes(pipeline.repo_id)),
);
const activePipelines = computed(() => pipelineFeed.value.filter(isPipelineActive));
@ -89,7 +82,7 @@ export const usePipelineStore = defineStore('pipelines', () => {
const _pipelines = await apiClient.getPipelineFeed();
_pipelines.forEach((pipeline) => {
setPipeline(pipeline.owner, pipeline.name, pipeline);
setPipeline(pipeline.repo_id, pipeline);
});
}

View file

@ -3,49 +3,45 @@ import { computed, reactive, Ref, ref } from 'vue';
import useApiClient from '~/compositions/useApiClient';
import { Repo } from '~/lib/api/types';
import { repoSlug } from '~/utils/helpers';
export const useRepoStore = defineStore('repos', () => {
const apiClient = useApiClient();
const repos: Map<string, Repo> = reactive(new Map());
const ownedRepoSlugs = ref<string[]>([]);
const repos: Map<number, Repo> = reactive(new Map());
const ownedRepoIds = ref<number[]>([]);
const ownedRepos = computed(() =>
Array.from(repos.entries())
.filter(([slug]) => ownedRepoSlugs.value.includes(slug))
.filter(([repoId]) => ownedRepoIds.value.includes(repoId))
.map(([, repo]) => repo),
);
function getRepo(owner: Ref<string>, name: Ref<string>) {
return computed(() => {
const slug = repoSlug(owner.value, name.value);
return repos.get(slug);
});
function getRepo(repoId: Ref<number>) {
return computed(() => repos.get(repoId.value));
}
function setRepo(repo: Repo) {
repos.set(repoSlug(repo), repo);
repos.set(repo.id, repo);
}
async function loadRepo(owner: string, name: string) {
const repo = await apiClient.getRepo(owner, name);
repos.set(repoSlug(repo), repo);
async function loadRepo(repoId: number) {
const repo = await apiClient.getRepo(repoId);
repos.set(repo.id, repo);
return repo;
}
async function loadRepos() {
const _ownedRepos = await apiClient.getRepoList();
_ownedRepos.forEach((repo) => {
repos.set(repoSlug(repo), repo);
repos.set(repo.id, repo);
});
ownedRepoSlugs.value = _ownedRepos.map((repo) => repoSlug(repo));
ownedRepoIds.value = _ownedRepos.map((repo) => repo.id);
}
return {
repos,
ownedRepos,
ownedRepoSlugs,
ownedRepoIds,
getRepo,
setRepo,
loadRepo,

View file

@ -9,7 +9,7 @@
v-for="repo in searchedRepos"
:key="repo.id"
class="items-center"
:to="repo.active ? { name: 'repo', params: { repoOwner: repo.owner, repoName: repo.name } } : undefined"
:to="repo.active ? { name: 'repo', params: { repoId: repo.id } } : undefined"
>
<span class="text-color">{{ repo.full_name }}</span>
<span v-if="repo.active" class="ml-auto text-color-alt">{{ $t('repo.enable.enabled') }}</span>
@ -25,8 +25,8 @@
</Scaffold>
</template>
<script lang="ts">
import { defineComponent, onMounted, ref } from 'vue';
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
@ -40,48 +40,27 @@ import { useRepoSearch } from '~/compositions/useRepoSearch';
import { useRouteBackOrDefault } from '~/compositions/useRouteBackOrDefault';
import { Repo } from '~/lib/api/types';
export default defineComponent({
name: 'RepoAdd',
const router = useRouter();
const apiClient = useApiClient();
const notifications = useNotifications();
const repos = ref<Repo[]>();
const repoToActivate = ref<Repo>();
const search = ref('');
const i18n = useI18n();
components: {
Button,
ListItem,
Scaffold,
},
const { searchedRepos } = useRepoSearch(repos, search);
setup() {
const router = useRouter();
const apiClient = useApiClient();
const notifications = useNotifications();
const repos = ref<Repo[]>();
const repoToActivate = ref<Repo>();
const search = ref('');
const i18n = useI18n();
const { searchedRepos } = useRepoSearch(repos, search);
onMounted(async () => {
repos.value = await apiClient.getRepoList({ all: true });
});
const { doSubmit: activateRepo, isLoading: isActivatingRepo } = useAsyncAction(async (repo: Repo) => {
repoToActivate.value = repo;
await apiClient.activateRepo(repo.owner, repo.name);
notifications.notify({ title: i18n.t('repo.enable.success'), type: 'success' });
repoToActivate.value = undefined;
await router.push({ name: 'repo', params: { repoName: repo.name, repoOwner: repo.owner } });
});
const goBack = useRouteBackOrDefault({ name: 'repos' });
return {
isActivatingRepo,
repoToActivate,
goBack,
activateRepo,
searchedRepos,
search,
};
},
onMounted(async () => {
repos.value = await apiClient.getRepoList({ all: true });
});
const { doSubmit: activateRepo, isLoading: isActivatingRepo } = useAsyncAction(async (repo: Repo) => {
repoToActivate.value = repo;
const _repo = await apiClient.activateRepo(repo.forge_remote_id);
notifications.notify({ title: i18n.t('repo.enable.success'), type: 'success' });
repoToActivate.value = undefined;
await router.push({ name: 'repo', params: { repoId: _repo.id } });
});
const goBack = useRouteBackOrDefault({ name: 'repos' });
</script>

View file

@ -9,11 +9,7 @@
</template>
<div class="space-y-4">
<ListItem
v-for="repo in searchedRepos"
:key="repo.id"
:to="{ name: 'repo', params: { repoName: repo.name, repoOwner: repo.owner } }"
>
<ListItem v-for="repo in searchedRepos" :key="repo.id" :to="{ name: 'repo', params: { repoId: repo.id } }">
<span class="text-color">{{ `${repo.owner} / ${repo.name}` }}</span>
</ListItem>
</div>

View file

@ -1,76 +0,0 @@
<template>
<Scaffold v-model:search="search">
<template #title>
{{ repoOwner }}
</template>
<template #titleActions>
<IconButton
v-if="orgPermissions.admin"
icon="settings"
:to="{ name: 'org-settings' }"
:title="$t('org.settings.settings')"
/>
</template>
<div class="space-y-4">
<ListItem
v-for="repo in searchedRepos"
:key="repo.id"
:to="{ name: 'repo', params: { repoName: repo.name, repoOwner: repo.owner } }"
>
<span class="text-color">{{ `${repo.name}` }}</span>
</ListItem>
</div>
<div v-if="(searchedRepos || []).length <= 0" class="text-center">
<span class="text-color m-auto">{{ $t('repo.user_none') }}</span>
</div>
</Scaffold>
</template>
<script lang="ts">
import { computed, defineComponent, onMounted, ref } from 'vue';
import IconButton from '~/components/atomic/IconButton.vue';
import ListItem from '~/components/atomic/ListItem.vue';
import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
import useApiClient from '~/compositions/useApiClient';
import { useRepoSearch } from '~/compositions/useRepoSearch';
import { OrgPermissions } from '~/lib/api/types';
import { useRepoStore } from '~/store/repos';
export default defineComponent({
name: 'ReposOwner',
components: {
ListItem,
IconButton,
Scaffold,
},
props: {
repoOwner: {
type: String,
required: true,
},
},
setup(props) {
const apiClient = useApiClient();
const repoStore = useRepoStore();
// TODO: filter server side
const repos = computed(() => Array.from(repoStore.repos.values()).filter((repo) => repo.owner === props.repoOwner));
const search = ref('');
const orgPermissions = ref<OrgPermissions>({ member: false, admin: false });
const { searchedRepos } = useRepoSearch(repos, search);
onMounted(async () => {
await repoStore.loadRepos();
orgPermissions.value = await apiClient.getOrgPermissions(props.repoOwner);
});
return { searchedRepos, search, orgPermissions };
},
});
</script>

View file

@ -0,0 +1,3 @@
<template>
<router-view />
</template>

View file

@ -0,0 +1,57 @@
<template>
<Scaffold v-model:search="search">
<template #title>
{{ orgName }}
</template>
<template #titleActions>
<IconButton
v-if="orgPermissions.admin"
icon="settings"
:to="{ name: 'org-settings' }"
:title="$t('org.settings.settings')"
/>
</template>
<div class="space-y-4">
<ListItem v-for="repo in searchedRepos" :key="repo.id" :to="{ name: 'repo', params: { repoId: repo.id } }">
<span class="text-color">{{ `${repo.owner} / ${repo.name}` }}</span>
</ListItem>
</div>
<div v-if="(searchedRepos || []).length <= 0" class="text-center">
<span class="text-color m-auto">{{ $t('repo.user_none') }}</span>
</div>
</Scaffold>
</template>
<script lang="ts" setup>
import { computed, onMounted, ref, toRef } from 'vue';
import IconButton from '~/components/atomic/IconButton.vue';
import ListItem from '~/components/atomic/ListItem.vue';
import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
import useApiClient from '~/compositions/useApiClient';
import { useRepoSearch } from '~/compositions/useRepoSearch';
import { OrgPermissions } from '~/lib/api/types';
import { useRepoStore } from '~/store/repos';
const props = defineProps<{
orgName: string;
}>();
const apiClient = useApiClient();
const repoStore = useRepoStore();
// TODO: filter server side
const orgName = toRef(props, 'orgName');
const repos = computed(() => Array.from(repoStore.repos.values()).filter((repo) => repo.owner === orgName.value));
const search = ref('');
const orgPermissions = ref<OrgPermissions>({ member: false, admin: false });
const { searchedRepos } = useRepoSearch(repos, search);
onMounted(async () => {
await repoStore.loadRepos();
orgPermissions.value = await apiClient.getOrgPermissions(orgName.value);
});
</script>

View file

@ -2,7 +2,7 @@
<Scaffold enable-tabs :go-back="goBack">
<template #title>
<span>
<router-link :to="{ name: 'repos-owner', params: { repoOwner: org.name } }" class="hover:underline">
<router-link :to="{ name: 'org', params: { orgName: org.name } }" class="hover:underline">
{{ org.name }}
</router-link>
/
@ -16,8 +16,8 @@
</Scaffold>
</template>
<script lang="ts">
import { defineComponent, inject, onMounted, Ref } from 'vue';
<script lang="ts" setup>
import { inject, onMounted, Ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
@ -27,40 +27,26 @@ import useNotifications from '~/compositions/useNotifications';
import { useRouteBackOrDefault } from '~/compositions/useRouteBackOrDefault';
import { Org, OrgPermissions } from '~/lib/api/types';
export default defineComponent({
name: 'OrgSettings',
const notifications = useNotifications();
const router = useRouter();
const i18n = useI18n();
components: {
Tab,
OrgSecretsTab,
},
const orgPermissions = inject<Ref<OrgPermissions>>('org-permissions');
if (!orgPermissions) {
throw new Error('Unexpected: "orgPermissions" should be provided at this place');
}
setup() {
const notifications = useNotifications();
const router = useRouter();
const i18n = useI18n();
const org = inject<Ref<Org>>('org');
if (!org) {
throw new Error('Unexpected: "org" should be provided at this place');
}
const orgPermissions = inject<Ref<OrgPermissions>>('org-permissions');
if (!orgPermissions) {
throw new Error('Unexpected: "orgPermissions" should be provided at this place');
}
const org = inject<Ref<Org>>('org');
if (!org) {
throw new Error('Unexpected: "org" should be provided at this place');
}
onMounted(async () => {
if (!orgPermissions.value.admin) {
notifications.notify({ type: 'error', title: i18n.t('org.settings.not_allowed') });
await router.replace({ name: 'home' });
}
});
return {
org,
goBack: useRouteBackOrDefault({ name: 'repos-owner' }),
};
},
onMounted(async () => {
if (!orgPermissions.value.admin) {
notifications.notify({ type: 'error', title: i18n.t('org.settings.not_allowed') });
await router.replace({ name: 'home' });
}
});
const goBack = useRouteBackOrDefault({ name: 'repos-owner' });
</script>

View file

@ -18,48 +18,35 @@
<router-view v-else-if="org && orgPermissions" />
</template>
<script lang="ts">
import { computed, defineComponent, onMounted, provide, ref, toRef, watch } from 'vue';
<script lang="ts" setup>
import { computed, onMounted, provide, ref, toRef, watch } from 'vue';
import IconButton from '~/components/atomic/IconButton.vue';
import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
import useApiClient from '~/compositions/useApiClient';
import { Org, OrgPermissions } from '~/lib/api/types';
export default defineComponent({
name: 'OrgWrapper',
const props = defineProps<{
orgName: string;
}>();
components: { IconButton, Scaffold },
const orgName = toRef(props, 'orgName');
const apiClient = useApiClient();
props: {
repoOwner: {
type: String,
required: true,
},
},
const org = computed<Org>(() => ({ name: orgName.value }));
const orgPermissions = ref<OrgPermissions>();
provide('org', org);
provide('org-permissions', orgPermissions);
setup(props) {
const repoOwner = toRef(props, 'repoOwner');
const apiClient = useApiClient();
const org = computed<Org>(() => ({ name: repoOwner.value }));
async function load() {
orgPermissions.value = await apiClient.getOrgPermissions(orgName.value);
}
const orgPermissions = ref<OrgPermissions>();
provide('org', org);
provide('org-permissions', orgPermissions);
onMounted(() => {
load();
});
async function load() {
orgPermissions.value = await apiClient.getOrgPermissions(repoOwner.value);
}
onMounted(() => {
load();
});
watch([repoOwner], () => {
load();
});
return { org, orgPermissions };
},
watch([orgName], () => {
load();
});
</script>

View file

@ -31,7 +31,7 @@ async function loadBranches(page: number): Promise<string[]> {
throw new Error('Unexpected: "repo" should be provided at this place');
}
return apiClient.getRepoBranches(repo.value.owner, repo.value.name, page);
return apiClient.getRepoBranches(repo.value.id, page);
}
const { resetPage, data: branches } = usePagination(loadBranches);

View file

@ -0,0 +1,54 @@
<template>
<div />
</template>
<script setup lang="ts">
import { onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import useApiClient from '~/compositions/useApiClient';
const apiClient = useApiClient();
const route = useRoute();
const router = useRouter();
const props = defineProps<{
repoOwner: string;
repoName: string;
}>();
onMounted(async () => {
const repo = await apiClient.lookupRepo(props.repoOwner, props.repoName);
// {
// path: ':pipelineId',
// redirect: (route) => ({ name: 'repo-pipeline', params: route.params }),
// },
// {
// path: 'build/:pipelineId',
// redirect: (route) => ({ name: 'repo-pipeline', params: route.params }),
// children: [
// {
// path: ':procId?',
// redirect: (route) => ({ name: 'repo-pipeline', params: route.params }),
// },
// {
// path: 'changed-files',
// redirect: (route) => ({ name: 'repo-pipeline-changed-files', params: route.params }),
// },
// {
// path: 'config',
// redirect: (route) => ({ name: 'repo-pipeline-config', params: route.params }),
// },
// ],
// },
// TODO: support pipeline and build routes
const path = route.path
.replace(`/repos/${props.repoOwner}/${props.repoName}`, `/repos/${repo?.id}`)
.replace(`/${props.repoOwner}/${props.repoName}`, `/repos/${repo?.id}`);
await router.replace({ path });
});
</script>

View file

@ -35,7 +35,7 @@ async function loadPullRequests(page: number): Promise<PullRequest[]> {
throw new Error('Unexpected: "repo" should be provided at this place');
}
return apiClient.getRepoPullRequests(repo.value.owner, repo.value.name, page);
return apiClient.getRepoPullRequests(repo.value.id, page);
}
const { resetPage, data: pullRequests } = usePagination(loadPullRequests);

View file

@ -2,14 +2,11 @@
<Scaffold enable-tabs :go-back="goBack">
<template #title>
<span>
<router-link :to="{ name: 'repos-owner', params: { repoOwner: repo.owner } }" class="hover:underline">
<router-link :to="{ name: 'org', params: { orgName: repo.owner } }" class="hover:underline">
{{ repo.owner }}
</router-link>
/
<router-link
:to="{ name: 'repo', params: { repoOwner: repo.owner, repoName: repo.name } }"
class="hover:underline"
>
<router-link :to="{ name: 'repo' }" class="hover:underline">
{{ repo.name }}
</router-link>
/

View file

@ -7,8 +7,8 @@
>
<template #title>
<span class="flex">
<router-link :to="{ name: 'repos-owner', params: { repoOwner } }" class="hover:underline">{{
repoOwner
<router-link :to="{ name: 'org', params: { orgName: repo?.owner } }" class="hover:underline">{{
repo.owner
}}</router-link>
{{ `&nbsp;/&nbsp;${repo.name}` }}
</span>
@ -72,20 +72,12 @@ import { RepoPermissions } from '~/lib/api/types';
import { usePipelineStore } from '~/store/pipelines';
import { useRepoStore } from '~/store/repos';
const props = defineProps({
repoOwner: {
type: String,
required: true,
},
const props = defineProps<{
repoId: string;
}>();
repoName: {
type: String,
required: true,
},
});
const repoOwner = toRef(props, 'repoOwner');
const repoName = toRef(props, 'repoName');
const _repoId = toRef(props, 'repoId');
const repoId = computed(() => parseInt(_repoId.value, 10));
const repoStore = useRepoStore();
const pipelineStore = usePipelineStore();
const apiClient = useApiClient();
@ -97,9 +89,9 @@ const i18n = useI18n();
const config = useConfig();
const { forge } = useConfig();
const repo = repoStore.getRepo(repoOwner, repoName);
const repo = repoStore.getRepo(repoId);
const repoPermissions = ref<RepoPermissions>();
const pipelines = pipelineStore.getRepoPipelines(repoOwner, repoName);
const pipelines = pipelineStore.getRepoPipelines(repoId);
provide('repo', repo);
provide('repo-permissions', repoPermissions);
provide('pipelines', pipelines);
@ -107,7 +99,7 @@ provide('pipelines', pipelines);
const showManualPipelinePopup = ref(false);
async function loadRepo() {
repoPermissions.value = await apiClient.getRepoPermissions(repoOwner.value, repoName.value);
repoPermissions.value = await apiClient.getRepoPermissions(repoId.value);
if (!repoPermissions.value.pull) {
notifications.notify({ type: 'error', title: i18n.t('repo.not_allowed') });
// no access and not authenticated, redirect to login
@ -119,26 +111,19 @@ async function loadRepo() {
return;
}
const apiRepo = await repoStore.loadRepo(repoOwner.value, repoName.value);
if (apiRepo.full_name !== `${repoOwner.value}/${repoName.value}`) {
await router.replace({
name: route.name ? route.name : 'repo',
params: { repoOwner: apiRepo.owner, repoName: apiRepo.name },
});
return;
}
await pipelineStore.loadRepoPipelines(repoOwner.value, repoName.value);
await repoStore.loadRepo(repoId.value);
await pipelineStore.loadRepoPipelines(repoId.value);
}
onMounted(() => {
loadRepo();
});
watch([repoOwner, repoName], () => {
watch([repoId], () => {
loadRepo();
});
const badgeUrl = computed(() => repo.value && `/api/badges/${repo.value.owner}/${repo.value.name}/status.svg`);
const badgeUrl = computed(() => repo.value && `/api/badges/${repo.value.id}/status.svg`);
const activeTab = computed({
get() {

View file

@ -133,7 +133,7 @@ const { doSubmit: approvePipeline, isLoading: isApprovingPipeline } = useAsyncAc
throw new Error('Unexpected: Repo is undefined');
}
await apiClient.approvePipeline(repo.value.owner, repo.value.name, `${pipeline.value.number}`);
await apiClient.approvePipeline(repo.value.id, `${pipeline.value.number}`);
notifications.notify({ title: i18n.t('repo.pipeline.protected.approve_success'), type: 'success' });
});
@ -142,7 +142,7 @@ const { doSubmit: declinePipeline, isLoading: isDecliningPipeline } = useAsyncAc
throw new Error('Unexpected: Repo is undefined');
}
await apiClient.declinePipeline(repo.value.owner, repo.value.name, `${pipeline.value.number}`);
await apiClient.declinePipeline(repo.value.id, `${pipeline.value.number}`);
notifications.notify({ title: i18n.t('repo.pipeline.protected.decline_success'), type: 'success' });
});
</script>

View file

@ -41,9 +41,7 @@ export default defineComponent({
throw new Error('Unexpected: "repo" & "pipeline" should be provided at this place');
}
pipelineConfigs.value = (
await apiClient.getPipelineConfig(repo.value.owner, repo.value.name, pipeline.value.number)
).map((i) => ({
pipelineConfigs.value = (await apiClient.getPipelineConfig(repo.value.id, pipeline.value.number)).map((i) => ({
...i,
data: atob(i.data),
}));

View file

@ -96,8 +96,7 @@ import { Repo, RepoPermissions } from '~/lib/api/types';
import { usePipelineStore } from '~/store/pipelines';
const props = defineProps<{
repoOwner: string;
repoName: string;
repoId: string;
pipelineId: string;
}>();
@ -110,15 +109,15 @@ const i18n = useI18n();
const pipelineStore = usePipelineStore();
const pipelineId = toRef(props, 'pipelineId');
const repoOwner = toRef(props, 'repoOwner');
const repoName = toRef(props, 'repoName');
const _repoId = toRef(props, 'repoId');
const repoId = computed(() => parseInt(_repoId.value, 10));
const repo = inject<Ref<Repo>>('repo');
const repoPermissions = inject<Ref<RepoPermissions>>('repo-permissions');
if (!repo || !repoPermissions) {
throw new Error('Unexpected: "repo" & "repoPermissions" should be provided at this place');
}
const pipeline = pipelineStore.getPipeline(repoOwner, repoName, pipelineId);
const pipeline = pipelineStore.getPipeline(repoId, pipelineId);
const { since, duration, created } = usePipeline(pipeline);
provide('pipeline', pipeline);
@ -131,7 +130,7 @@ async function loadPipeline(): Promise<void> {
throw new Error('Unexpected: Repo is undefined');
}
await pipelineStore.loadPipeline(repo.value.owner, repo.value.name, parseInt(pipelineId.value, 10));
await pipelineStore.loadPipeline(repo.value.id, parseInt(pipelineId.value, 10));
favicon.updateStatus(pipeline.value?.status);
}
@ -152,7 +151,7 @@ const { doSubmit: cancelPipeline, isLoading: isCancelingPipeline } = useAsyncAct
// throw new Error('Unexpected: Step not found');
// }
await apiClient.cancelPipeline(repo.value.owner, repo.value.name, parseInt(pipelineId.value, 10));
await apiClient.cancelPipeline(repo.value.id, parseInt(pipelineId.value, 10));
notifications.notify({ title: i18n.t('repo.pipeline.actions.cancel_success'), type: 'success' });
});
@ -161,7 +160,7 @@ const { doSubmit: restartPipeline, isLoading: isRestartingPipeline } = useAsyncA
throw new Error('Unexpected: Repo is undefined');
}
const newPipeline = await apiClient.restartPipeline(repo.value.owner, repo.value.name, pipelineId.value, {
const newPipeline = await apiClient.restartPipeline(repo.value.id, pipelineId.value, {
fork: true,
});
notifications.notify({ title: i18n.t('repo.pipeline.actions.restart_success'), type: 'success' });
@ -172,7 +171,7 @@ const { doSubmit: restartPipeline, isLoading: isRestartingPipeline } = useAsyncA
});
onMounted(loadPipeline);
watch([repoName, repoOwner, pipelineId], loadPipeline);
watch([repoId, pipelineId], loadPipeline);
onBeforeUnmount(() => {
favicon.updateStatus('default');
});

View file

@ -28,23 +28,24 @@ import (
const (
pathSelf = "%s/api/user"
pathRepos = "%s/api/user/repos"
pathRepo = "%s/api/repos/%s/%s"
pathRepoMove = "%s/api/repos/%s/%s/move?to=%s"
pathChown = "%s/api/repos/%s/%s/chown"
pathRepair = "%s/api/repos/%s/%s/repair"
pathPipelines = "%s/api/repos/%s/%s/pipelines"
pathPipeline = "%s/api/repos/%s/%s/pipelines/%v"
pathLogs = "%s/api/repos/%s/%s/logs/%d/%d"
pathApprove = "%s/api/repos/%s/%s/pipelines/%d/approve"
pathDecline = "%s/api/repos/%s/%s/pipelines/%d/decline"
pathStop = "%s/api/repos/%s/%s/pipelines/%d/cancel"
pathLogPurge = "%s/api/repos/%s/%s/logs/%d"
pathRepoSecrets = "%s/api/repos/%s/%s/secrets"
pathRepoSecret = "%s/api/repos/%s/%s/secrets/%s"
pathRepoRegistries = "%s/api/repos/%s/%s/registry"
pathRepoRegistry = "%s/api/repos/%s/%s/registry/%s"
pathRepoCrons = "%s/api/repos/%s/%s/cron"
pathRepoCron = "%s/api/repos/%s/%s/cron/%d"
pathRepo = "%s/api/repos/%d"
pathRepoLookup = "%s/api/repos/lookup/%s"
pathRepoMove = "%s/api/repos/%d/move?to=%s"
pathChown = "%s/api/repos/%d/chown"
pathRepair = "%s/api/repos/%d/repair"
pathPipelines = "%s/api/repos/%d/pipelines"
pathPipeline = "%s/api/repos/%d/pipelines/%v"
pathLogs = "%s/api/repos/%d/logs/%d/%d"
pathApprove = "%s/api/repos/%d/pipelines/%d/approve"
pathDecline = "%s/api/repos/%d/pipelines/%d/decline"
pathStop = "%s/api/repos/%d/pipelines/%d/cancel"
pathLogPurge = "%s/api/repos/%d/logs/%d"
pathRepoSecrets = "%s/api/repos/%d/secrets"
pathRepoSecret = "%s/api/repos/%d/secrets/%s"
pathRepoRegistries = "%s/api/repos/%d/registry"
pathRepoRegistry = "%s/api/repos/%d/registry/%s"
pathRepoCrons = "%s/api/repos/%d/cron"
pathRepoCron = "%s/api/repos/%d/cron/%d"
pathOrgSecrets = "%s/api/orgs/%s/secrets"
pathOrgSecret = "%s/api/orgs/%s/secrets/%s"
pathGlobalSecrets = "%s/api/secrets"
@ -134,10 +135,18 @@ func (c *client) UserDel(login string) error {
return err
}
// Repo returns a repository by name.
func (c *client) Repo(owner, name string) (*Repo, error) {
// Repo returns a repository by id.
func (c *client) Repo(repoID int64) (*Repo, error) {
out := new(Repo)
uri := fmt.Sprintf(pathRepo, c.addr, owner, name)
uri := fmt.Sprintf(pathRepo, c.addr, repoID)
err := c.get(uri, out)
return out, err
}
// RepoLookup returns a repository by name.
func (c *client) RepoLookup(fullName string) (*Repo, error) {
out := new(Repo)
uri := fmt.Sprintf(pathRepoLookup, c.addr, fullName)
err := c.get(uri, out)
return out, err
}
@ -161,60 +170,60 @@ func (c *client) RepoListOpts(sync, all bool) ([]*Repo, error) {
}
// RepoPost activates a repository.
func (c *client) RepoPost(owner, name string) (*Repo, error) {
func (c *client) RepoPost(forgeRemoteID int64) (*Repo, error) {
out := new(Repo)
uri := fmt.Sprintf(pathRepo, c.addr, owner, name)
uri := fmt.Sprintf(pathRepo, c.addr, forgeRemoteID)
err := c.post(uri, nil, out)
return out, err
}
// RepoChown updates a repository owner.
func (c *client) RepoChown(owner, name string) (*Repo, error) {
func (c *client) RepoChown(repoID int64) (*Repo, error) {
out := new(Repo)
uri := fmt.Sprintf(pathChown, c.addr, owner, name)
uri := fmt.Sprintf(pathChown, c.addr, repoID)
err := c.post(uri, nil, out)
return out, err
}
// RepoRepair repairs the repository hooks.
func (c *client) RepoRepair(owner, name string) error {
uri := fmt.Sprintf(pathRepair, c.addr, owner, name)
func (c *client) RepoRepair(repoID int64) error {
uri := fmt.Sprintf(pathRepair, c.addr, repoID)
return c.post(uri, nil, nil)
}
// RepoPatch updates a repository.
func (c *client) RepoPatch(owner, name string, in *RepoPatch) (*Repo, error) {
func (c *client) RepoPatch(repoID int64, in *RepoPatch) (*Repo, error) {
out := new(Repo)
uri := fmt.Sprintf(pathRepo, c.addr, owner, name)
uri := fmt.Sprintf(pathRepo, c.addr, repoID)
err := c.patch(uri, in, out)
return out, err
}
// RepoDel deletes a repository.
func (c *client) RepoDel(owner, name string) error {
uri := fmt.Sprintf(pathRepo, c.addr, owner, name)
func (c *client) RepoDel(repoID int64) error {
uri := fmt.Sprintf(pathRepo, c.addr, repoID)
err := c.delete(uri)
return err
}
// RepoMove moves a repository
func (c *client) RepoMove(owner, name, newFullName string) error {
uri := fmt.Sprintf(pathRepoMove, c.addr, owner, name, newFullName)
func (c *client) RepoMove(repoID int64, newFullName string) error {
uri := fmt.Sprintf(pathRepoMove, c.addr, repoID, newFullName)
return c.post(uri, nil, nil)
}
// Pipeline returns a repository pipeline by number.
func (c *client) Pipeline(owner, name string, num int) (*Pipeline, error) {
// Pipeline returns a repository pipeline by pipeline-id.
func (c *client) Pipeline(repoID int64, pipeline int) (*Pipeline, error) {
out := new(Pipeline)
uri := fmt.Sprintf(pathPipeline, c.addr, owner, name, num)
uri := fmt.Sprintf(pathPipeline, c.addr, repoID, pipeline)
err := c.get(uri, out)
return out, err
}
// Pipeline returns the latest repository pipeline by branch.
func (c *client) PipelineLast(owner, name, branch string) (*Pipeline, error) {
func (c *client) PipelineLast(repoID int64, branch string) (*Pipeline, error) {
out := new(Pipeline)
uri := fmt.Sprintf(pathPipeline, c.addr, owner, name, "latest")
uri := fmt.Sprintf(pathPipeline, c.addr, repoID, "latest")
if len(branch) != 0 {
uri += "?branch=" + branch
}
@ -224,16 +233,16 @@ func (c *client) PipelineLast(owner, name, branch string) (*Pipeline, error) {
// PipelineList returns a list of recent pipelines for the
// the specified repository.
func (c *client) PipelineList(owner, name string) ([]*Pipeline, error) {
func (c *client) PipelineList(repoID int64) ([]*Pipeline, error) {
var out []*Pipeline
uri := fmt.Sprintf(pathPipelines, c.addr, owner, name)
uri := fmt.Sprintf(pathPipelines, c.addr, repoID)
err := c.get(uri, &out)
return out, err
}
func (c *client) PipelineCreate(owner, name string, options *PipelineOptions) (*Pipeline, error) {
func (c *client) PipelineCreate(repoID int64, options *PipelineOptions) (*Pipeline, error) {
var out *Pipeline
uri := fmt.Sprintf(pathPipelines, c.addr, owner, name)
uri := fmt.Sprintf(pathPipelines, c.addr, repoID)
err := c.post(uri, options, &out)
return out, err
}
@ -247,47 +256,47 @@ func (c *client) PipelineQueue() ([]*Activity, error) {
}
// PipelineStart re-starts a stopped pipeline.
func (c *client) PipelineStart(owner, name string, num int, params map[string]string) (*Pipeline, error) {
func (c *client) PipelineStart(repoID int64, pipeline int, params map[string]string) (*Pipeline, error) {
out := new(Pipeline)
val := mapValues(params)
uri := fmt.Sprintf(pathPipeline, c.addr, owner, name, num)
uri := fmt.Sprintf(pathPipeline, c.addr, repoID, pipeline)
err := c.post(uri+"?"+val.Encode(), nil, out)
return out, err
}
// PipelineStop cancels the running step.
func (c *client) PipelineStop(owner, name string, pipeline int) error {
uri := fmt.Sprintf(pathStop, c.addr, owner, name, pipeline)
func (c *client) PipelineStop(repoID int64, pipeline int) error {
uri := fmt.Sprintf(pathStop, c.addr, repoID, pipeline)
err := c.post(uri, nil, nil)
return err
}
// PipelineApprove approves a blocked pipeline.
func (c *client) PipelineApprove(owner, name string, num int) (*Pipeline, error) {
func (c *client) PipelineApprove(repoID int64, pipeline int) (*Pipeline, error) {
out := new(Pipeline)
uri := fmt.Sprintf(pathApprove, c.addr, owner, name, num)
uri := fmt.Sprintf(pathApprove, c.addr, repoID, pipeline)
err := c.post(uri, nil, out)
return out, err
}
// PipelineDecline declines a blocked pipeline.
func (c *client) PipelineDecline(owner, name string, num int) (*Pipeline, error) {
func (c *client) PipelineDecline(repoID int64, pipeline int) (*Pipeline, error) {
out := new(Pipeline)
uri := fmt.Sprintf(pathDecline, c.addr, owner, name, num)
uri := fmt.Sprintf(pathDecline, c.addr, repoID, pipeline)
err := c.post(uri, nil, out)
return out, err
}
// PipelineKill force kills the running pipeline.
func (c *client) PipelineKill(owner, name string, num int) error {
uri := fmt.Sprintf(pathPipeline, c.addr, owner, name, num)
func (c *client) PipelineKill(repoID int64, pipeline int) error {
uri := fmt.Sprintf(pathPipeline, c.addr, repoID, pipeline)
err := c.delete(uri)
return err
}
// PipelineLogs returns the pipeline logs for the specified step.
func (c *client) StepLogEntries(owner, name string, num, step int) ([]*LogEntry, error) {
uri := fmt.Sprintf(pathLogs, c.addr, owner, name, num, step)
func (c *client) StepLogEntries(repoID int64, num, step int) ([]*LogEntry, error) {
uri := fmt.Sprintf(pathLogs, c.addr, repoID, num, step)
var out []*LogEntry
err := c.get(uri, &out)
return out, err
@ -295,96 +304,96 @@ func (c *client) StepLogEntries(owner, name string, num, step int) ([]*LogEntry,
// Deploy triggers a deployment for an existing pipeline using the
// specified target environment.
func (c *client) Deploy(owner, name string, num int, env string, params map[string]string) (*Pipeline, error) {
func (c *client) Deploy(repoID int64, pipeline int, env string, params map[string]string) (*Pipeline, error) {
out := new(Pipeline)
val := mapValues(params)
val.Set("event", EventDeploy)
val.Set("deploy_to", env)
uri := fmt.Sprintf(pathPipeline, c.addr, owner, name, num)
uri := fmt.Sprintf(pathPipeline, c.addr, repoID, pipeline)
err := c.post(uri+"?"+val.Encode(), nil, out)
return out, err
}
// LogsPurge purges the pipeline logs for the specified pipeline.
func (c *client) LogsPurge(owner, name string, num int) error {
uri := fmt.Sprintf(pathLogPurge, c.addr, owner, name, num)
func (c *client) LogsPurge(repoID int64, pipeline int) error {
uri := fmt.Sprintf(pathLogPurge, c.addr, repoID, pipeline)
err := c.delete(uri)
return err
}
// Registry returns a registry by hostname.
func (c *client) Registry(owner, name, hostname string) (*Registry, error) {
func (c *client) Registry(repoID int64, hostname string) (*Registry, error) {
out := new(Registry)
uri := fmt.Sprintf(pathRepoRegistry, c.addr, owner, name, hostname)
uri := fmt.Sprintf(pathRepoRegistry, c.addr, repoID, hostname)
err := c.get(uri, out)
return out, err
}
// RegistryList returns a list of all repository registries.
func (c *client) RegistryList(owner, name string) ([]*Registry, error) {
func (c *client) RegistryList(repoID int64) ([]*Registry, error) {
var out []*Registry
uri := fmt.Sprintf(pathRepoRegistries, c.addr, owner, name)
uri := fmt.Sprintf(pathRepoRegistries, c.addr, repoID)
err := c.get(uri, &out)
return out, err
}
// RegistryCreate creates a registry.
func (c *client) RegistryCreate(owner, name string, in *Registry) (*Registry, error) {
func (c *client) RegistryCreate(repoID int64, in *Registry) (*Registry, error) {
out := new(Registry)
uri := fmt.Sprintf(pathRepoRegistries, c.addr, owner, name)
uri := fmt.Sprintf(pathRepoRegistries, c.addr, repoID)
err := c.post(uri, in, out)
return out, err
}
// RegistryUpdate updates a registry.
func (c *client) RegistryUpdate(owner, name string, in *Registry) (*Registry, error) {
func (c *client) RegistryUpdate(repoID int64, in *Registry) (*Registry, error) {
out := new(Registry)
uri := fmt.Sprintf(pathRepoRegistry, c.addr, owner, name, in.Address)
uri := fmt.Sprintf(pathRepoRegistry, c.addr, repoID, in.Address)
err := c.patch(uri, in, out)
return out, err
}
// RegistryDelete deletes a registry.
func (c *client) RegistryDelete(owner, name, hostname string) error {
uri := fmt.Sprintf(pathRepoRegistry, c.addr, owner, name, hostname)
func (c *client) RegistryDelete(repoID int64, hostname string) error {
uri := fmt.Sprintf(pathRepoRegistry, c.addr, repoID, hostname)
return c.delete(uri)
}
// Secret returns a secret by name.
func (c *client) Secret(owner, name, secret string) (*Secret, error) {
func (c *client) Secret(repoID int64, secret string) (*Secret, error) {
out := new(Secret)
uri := fmt.Sprintf(pathRepoSecret, c.addr, owner, name, secret)
uri := fmt.Sprintf(pathRepoSecret, c.addr, repoID, secret)
err := c.get(uri, out)
return out, err
}
// SecretList returns a list of all repository secrets.
func (c *client) SecretList(owner, name string) ([]*Secret, error) {
func (c *client) SecretList(repoID int64) ([]*Secret, error) {
var out []*Secret
uri := fmt.Sprintf(pathRepoSecrets, c.addr, owner, name)
uri := fmt.Sprintf(pathRepoSecrets, c.addr, repoID)
err := c.get(uri, &out)
return out, err
}
// SecretCreate creates a secret.
func (c *client) SecretCreate(owner, name string, in *Secret) (*Secret, error) {
func (c *client) SecretCreate(repoID int64, in *Secret) (*Secret, error) {
out := new(Secret)
uri := fmt.Sprintf(pathRepoSecrets, c.addr, owner, name)
uri := fmt.Sprintf(pathRepoSecrets, c.addr, repoID)
err := c.post(uri, in, out)
return out, err
}
// SecretUpdate updates a secret.
func (c *client) SecretUpdate(owner, name string, in *Secret) (*Secret, error) {
func (c *client) SecretUpdate(repoID int64, in *Secret) (*Secret, error) {
out := new(Secret)
uri := fmt.Sprintf(pathRepoSecret, c.addr, owner, name, in.Name)
uri := fmt.Sprintf(pathRepoSecret, c.addr, repoID, in.Name)
err := c.patch(uri, in, out)
return out, err
}
// SecretDelete deletes a secret.
func (c *client) SecretDelete(owner, name, secret string) error {
uri := fmt.Sprintf(pathRepoSecret, c.addr, owner, name, secret)
func (c *client) SecretDelete(repoID int64, secret string) error {
uri := fmt.Sprintf(pathRepoSecret, c.addr, repoID, secret)
return c.delete(uri)
}
@ -488,33 +497,33 @@ func (c *client) SetLogLevel(in *LogLevel) (*LogLevel, error) {
return out, err
}
func (c *client) CronList(owner, repo string) ([]*Cron, error) {
func (c *client) CronList(repoID int64) ([]*Cron, error) {
out := make([]*Cron, 0, 5)
uri := fmt.Sprintf(pathRepoCrons, c.addr, owner, repo)
uri := fmt.Sprintf(pathRepoCrons, c.addr, repoID)
return out, c.get(uri, &out)
}
func (c *client) CronCreate(owner, repo string, in *Cron) (*Cron, error) {
func (c *client) CronCreate(repoID int64, in *Cron) (*Cron, error) {
out := new(Cron)
uri := fmt.Sprintf(pathRepoCrons, c.addr, owner, repo)
uri := fmt.Sprintf(pathRepoCrons, c.addr, repoID)
return out, c.post(uri, in, out)
}
func (c *client) CronUpdate(owner, repo string, in *Cron) (*Cron, error) {
func (c *client) CronUpdate(repoID int64, in *Cron) (*Cron, error) {
out := new(Cron)
uri := fmt.Sprintf(pathRepoCron, c.addr, owner, repo, in.ID)
uri := fmt.Sprintf(pathRepoCron, c.addr, repoID, in.ID)
err := c.patch(uri, in, out)
return out, err
}
func (c *client) CronDelete(owner, repo string, cronID int64) error {
uri := fmt.Sprintf(pathRepoCron, c.addr, owner, repo, cronID)
func (c *client) CronDelete(repoID, cronID int64) error {
uri := fmt.Sprintf(pathRepoCron, c.addr, repoID, cronID)
return c.delete(uri)
}
func (c *client) CronGet(owner, repo string, cronID int64) (*Cron, error) {
func (c *client) CronGet(repoID, cronID int64) (*Cron, error) {
out := new(Cron)
uri := fmt.Sprintf(pathRepoCron, c.addr, owner, repo, cronID)
uri := fmt.Sprintf(pathRepoCron, c.addr, repoID, cronID)
return out, c.get(uri, out)
}

View file

@ -45,7 +45,10 @@ type Client interface {
UserDel(string) error
// Repo returns a repository by name.
Repo(string, string) (*Repo, error)
Repo(repoID int64) (*Repo, error)
// RepoLookup returns a repository id by the owner and name.
RepoLookup(repoFullName string) (*Repo, error)
// RepoList returns a list of all repositories to which the user has explicit
// access in the host system.
@ -56,94 +59,94 @@ type Client interface {
RepoListOpts(bool, bool) ([]*Repo, error)
// RepoPost activates a repository.
RepoPost(string, string) (*Repo, error)
RepoPost(forgeRemoteID int64) (*Repo, error)
// RepoPatch updates a repository.
RepoPatch(string, string, *RepoPatch) (*Repo, error)
RepoPatch(repoID int64, repo *RepoPatch) (*Repo, error)
// RepoMove moves the repository
RepoMove(string, string, string) error
RepoMove(repoID int64, dst string) error
// RepoChown updates a repository owner.
RepoChown(string, string) (*Repo, error)
RepoChown(repoID int64) (*Repo, error)
// RepoRepair repairs the repository hooks.
RepoRepair(string, string) error
RepoRepair(repoID int64) error
// RepoDel deletes a repository.
RepoDel(string, string) error
RepoDel(repoID int64) error
// Pipeline returns a repository pipeline by number.
Pipeline(string, string, int) (*Pipeline, error)
Pipeline(repoID int64, pipeline int) (*Pipeline, error)
// PipelineLast returns the latest repository pipeline by branch. An empty branch
// will result in the default branch.
PipelineLast(string, string, string) (*Pipeline, error)
PipelineLast(repoID int64, branch string) (*Pipeline, error)
// PipelineList returns a list of recent pipelines for the
// the specified repository.
PipelineList(string, string) ([]*Pipeline, error)
PipelineList(repoID int64) ([]*Pipeline, error)
// PipelineQueue returns a list of enqueued pipelines.
PipelineQueue() ([]*Activity, error)
// PipelineCreate returns creates a pipeline on specified branch.
PipelineCreate(string, string, *PipelineOptions) (*Pipeline, error)
PipelineCreate(repoID int64, opts *PipelineOptions) (*Pipeline, error)
// PipelineStart re-starts a stopped pipeline.
PipelineStart(string, string, int, map[string]string) (*Pipeline, error)
PipelineStart(repoID int64, num int, params map[string]string) (*Pipeline, error)
// PipelineStop stops the given pipeline.
PipelineStop(string, string, int) error
PipelineStop(repoID int64, pipeline int) error
// PipelineApprove approves a blocked pipeline.
PipelineApprove(string, string, int) (*Pipeline, error)
PipelineApprove(repoID int64, pipeline int) (*Pipeline, error)
// PipelineDecline declines a blocked pipeline.
PipelineDecline(string, string, int) (*Pipeline, error)
PipelineDecline(repoID int64, pipeline int) (*Pipeline, error)
// PipelineKill force kills the running pipeline.
PipelineKill(string, string, int) error
PipelineKill(repoID int64, pipeline int) error
// StepLogEntries returns the LogEntries for the given pipeline step
StepLogEntries(string, string, int, int) ([]*LogEntry, error)
StepLogEntries(repoID int64, pipeline, stepID int) ([]*LogEntry, error)
// Deploy triggers a deployment for an existing pipeline using the specified
// target environment.
Deploy(string, string, int, string, map[string]string) (*Pipeline, error)
Deploy(repoID int64, pipeline int, env string, params map[string]string) (*Pipeline, error)
// LogsPurge purges the pipeline logs for the specified pipeline.
LogsPurge(string, string, int) error
LogsPurge(repoID int64, pipeline int) error
// Registry returns a registry by hostname.
Registry(owner, name, hostname string) (*Registry, error)
Registry(repoID int64, hostname string) (*Registry, error)
// RegistryList returns a list of all repository registries.
RegistryList(owner, name string) ([]*Registry, error)
RegistryList(repoID int64) ([]*Registry, error)
// RegistryCreate creates a registry.
RegistryCreate(owner, name string, registry *Registry) (*Registry, error)
RegistryCreate(repoID int64, registry *Registry) (*Registry, error)
// RegistryUpdate updates a registry.
RegistryUpdate(owner, name string, registry *Registry) (*Registry, error)
RegistryUpdate(repoID int64, registry *Registry) (*Registry, error)
// RegistryDelete deletes a registry.
RegistryDelete(owner, name, hostname string) error
RegistryDelete(repoID int64, hostname string) error
// Secret returns a secret by name.
Secret(owner, name, secret string) (*Secret, error)
Secret(repoID int64, secret string) (*Secret, error)
// SecretList returns a list of all repository secrets.
SecretList(owner, name string) ([]*Secret, error)
SecretList(repoID int64) ([]*Secret, error)
// SecretCreate creates a secret.
SecretCreate(owner, name string, secret *Secret) (*Secret, error)
SecretCreate(repoID int64, secret *Secret) (*Secret, error)
// SecretUpdate updates a secret.
SecretUpdate(owner, name string, secret *Secret) (*Secret, error)
SecretUpdate(repoID int64, secret *Secret) (*Secret, error)
// SecretDelete deletes a secret.
SecretDelete(owner, name, secret string) error
SecretDelete(repoID int64, secret string) error
// OrgSecret returns an organization secret by name.
OrgSecret(owner, secret string) (*Secret, error)
@ -185,19 +188,19 @@ type Client interface {
SetLogLevel(logLevel *LogLevel) (*LogLevel, error)
// CronList list all cron jobs of a repo
CronList(owner, repo string) ([]*Cron, error)
CronList(repoID int64) ([]*Cron, error)
// CronGet get a specific cron job of a repo by id
CronGet(owner, repo string, cronID int64) (*Cron, error)
CronGet(repoID, cronID int64) (*Cron, error)
// CronDelete delete a specific cron job of a repo by id
CronDelete(owner, repo string, cronID int64) error
CronDelete(repoID, cronID int64) error
// CronCreate create a new cron job in a repo
CronCreate(owner, repo string, cron *Cron) (*Cron, error)
CronCreate(repoID int64, cron *Cron) (*Cron, error)
// CronUpdate update an existing cron job of a repo
CronUpdate(owner, repo string, cron *Cron) (*Cron, error)
CronUpdate(repoID int64, cron *Cron) (*Cron, error)
// AgentList returns a list of all registered agents
AgentList() ([]*Agent, error)

View file

@ -27,23 +27,26 @@ type (
// Repo represents a repository.
Repo struct {
ID int64 `json:"id,omitempty"`
Owner string `json:"owner"`
Name string `json:"name"`
FullName string `json:"full_name"`
Avatar string `json:"avatar_url,omitempty"`
Link string `json:"link_url,omitempty"`
Kind string `json:"scm,omitempty"`
Clone string `json:"clone_url,omitempty"`
Branch string `json:"default_branch,omitempty"`
Timeout int64 `json:"timeout,omitempty"`
Visibility string `json:"visibility"`
IsPrivate bool `json:"private,omitempty"`
IsTrusted bool `json:"trusted"`
IsStarred bool `json:"starred,omitempty"`
IsGated bool `json:"gated"`
AllowPull bool `json:"allow_pr"`
Config string `json:"config_file"`
ID int64 `json:"id,omitempty"`
ForgeRemoteID string `json:"forge_remote_id"`
Owner string `json:"owner"`
Name string `json:"name"`
FullName string `json:"full_name"`
Avatar string `json:"avatar_url,omitempty"`
Link string `json:"link_url,omitempty"`
Clone string `json:"clone_url,omitempty"`
DefaultBranch string `json:"default_branch,omitempty"`
SCMKind string `json:"scm,omitempty"`
Timeout int64 `json:"timeout,omitempty"`
Visibility string `json:"visibility"`
IsSCMPrivate bool `json:"private"`
IsTrusted bool `json:"trusted"`
IsGated bool `json:"gated"`
IsActive bool `json:"active"`
AllowPullRequests bool `json:"allow_pr"`
Config string `json:"config_file"`
CancelPreviousPipelineEvents []string `json:"cancel_previous_pipeline_events"`
NetrcOnlyTrusted bool `json:"netrc_only_trusted"`
}
// RepoPatch defines a repository patch request.