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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -13,7 +13,7 @@ import (
var repoInfoCmd = &cli.Command{ var repoInfoCmd = &cli.Command{
Name: "info", Name: "info",
Usage: "show repository details", Usage: "show repository details",
ArgsUsage: "<repo/name>", ArgsUsage: "<repo-id|repo-full-name>",
Action: repoInfo, Action: repoInfo,
Flags: append(common.GlobalFlags, Flags: append(common.GlobalFlags,
common.FormatFlag(tmplRepoInfo), common.FormatFlag(tmplRepoInfo),
@ -21,18 +21,17 @@ var repoInfoCmd = &cli.Command{
} }
func repoInfo(c *cli.Context) error { func repoInfo(c *cli.Context) error {
arg := c.Args().First() repoIDOrFullName := c.Args().First()
owner, name, err := internal.ParseRepo(arg)
if err != nil {
return err
}
client, err := internal.NewClient(c) client, err := internal.NewClient(c)
if err != nil { if err != nil {
return err 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 { if err != nil {
return err return err
} }
@ -47,11 +46,12 @@ func repoInfo(c *cli.Context) error {
// template for repo information // template for repo information
var tmplRepoInfo = `Owner: {{ .Owner }} var tmplRepoInfo = `Owner: {{ .Owner }}
Repo: {{ .Name }} Repo: {{ .Name }}
Type: {{ .Kind }} Link: {{ .Link }}
Config: {{ .Config }} Config path: {{ .Config }}
Visibility: {{ .Visibility }} Visibility: {{ .Visibility }}
Private: {{ .IsSCMPrivate }} Private: {{ .IsSCMPrivate }}
Trusted: {{ .IsTrusted }} Trusted: {{ .IsTrusted }}
Gated: {{ .IsGated }} 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 // 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 package repo
import ( import (
"fmt"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"github.com/woodpecker-ci/woodpecker/cli/common" "github.com/woodpecker-ci/woodpecker/cli/common"
@ -10,20 +12,26 @@ import (
var repoRepairCmd = &cli.Command{ var repoRepairCmd = &cli.Command{
Name: "repair", Name: "repair",
Usage: "repair repository webhooks", Usage: "repair repository webhooks",
ArgsUsage: "<repo/name>", ArgsUsage: "<repo-id|repo-full-name>",
Action: repoRepair, Action: repoRepair,
Flags: common.GlobalFlags, Flags: common.GlobalFlags,
} }
func repoRepair(c *cli.Context) error { func repoRepair(c *cli.Context) error {
repo := c.Args().First() repoIDOrFullName := c.Args().First()
owner, name, err := internal.ParseRepo(repo)
if err != nil {
return err
}
client, err := internal.NewClient(c) client, err := internal.NewClient(c)
if err != nil { if err != nil {
return err 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{ var repoRemoveCmd = &cli.Command{
Name: "rm", Name: "rm",
Usage: "remove a repository", Usage: "remove a repository",
ArgsUsage: "<repo/name>", ArgsUsage: "<repo-id|repo-full-name>",
Action: repoRemove, Action: repoRemove,
Flags: common.GlobalFlags, Flags: common.GlobalFlags,
} }
func repoRemove(c *cli.Context) error { func repoRemove(c *cli.Context) error {
repo := c.Args().First() repoIDOrFullName := c.Args().First()
owner, name, err := internal.ParseRepo(repo)
if err != nil {
return err
}
client, err := internal.NewClient(c) client, err := internal.NewClient(c)
if err != nil { if err != nil {
return err return err
} }
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err := client.RepoDel(owner, name); err != nil { if err != nil {
return err 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 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 { func repoSync(c *cli.Context) error {
client, err := internal.NewClient(c) client, err := internal.NewClient(c)
if err != nil { if err != nil {

View file

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

View file

@ -7,6 +7,7 @@ import (
"github.com/woodpecker-ci/woodpecker/cli/common" "github.com/woodpecker-ci/woodpecker/cli/common"
"github.com/woodpecker-ci/woodpecker/cli/internal" "github.com/woodpecker-ci/woodpecker/cli/internal"
"github.com/woodpecker-ci/woodpecker/woodpecker-go/woodpecker"
) )
// Command exports the secret command. // 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") { 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") orgName := c.String("organization")
repoName := c.String("repository") if orgName != "" && repoIDOrFullName == "" {
if orgName == "" && repoName == "" { return false, orgName, -1, err
repoName = c.Args().First()
} }
if orgName == "" && !strings.Contains(repoName, "/") {
orgName = repoName if orgName != "" && !strings.Contains(repoIDOrFullName, "/") {
repoIDOrFullName = orgName + "/" + repoIDOrFullName
} }
if orgName != "" {
return false, orgName, "", err repoID, err = internal.ParseRepo(client, repoIDOrFullName)
}
owner, name, err = internal.ParseRepo(repoName)
if err != nil { 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{ var secretCreateCmd = &cli.Command{
Name: "add", Name: "add",
Usage: "adds a secret", Usage: "adds a secret",
ArgsUsage: "[org/repo|org]", ArgsUsage: "[repo-id|repo-full-name]",
Action: secretCreate, Action: secretCreate,
Flags: append(common.GlobalFlags, Flags: append(common.GlobalFlags,
&cli.BoolFlag{ &cli.BoolFlag{
@ -74,7 +74,7 @@ func secretCreate(c *cli.Context) error {
secret.Value = string(out) secret.Value = string(out)
} }
global, owner, repo, err := parseTargetArgs(c) global, owner, repoID, err := parseTargetArgs(client, c)
if err != nil { if err != nil {
return err return err
} }
@ -83,11 +83,11 @@ func secretCreate(c *cli.Context) error {
_, err = client.GlobalSecretCreate(secret) _, err = client.GlobalSecretCreate(secret)
return err return err
} }
if repo == "" { if owner != "" {
_, err = client.OrgSecretCreate(owner, secret) _, err = client.OrgSecretCreate(owner, secret)
return err return err
} }
_, err = client.SecretCreate(owner, repo, secret) _, err = client.SecretCreate(repoID, secret)
return err return err
} }

View file

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

View file

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

View file

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

View file

@ -250,7 +250,7 @@ const docTemplate = `{
} }
} }
}, },
"/badges/{owner}/{name}/cc.xml": { "/badges/{repo_id}/cc.xml": {
"get": { "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/", "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": [ "produces": [
@ -283,7 +283,7 @@ const docTemplate = `{
} }
} }
}, },
"/badges/{owner}/{name}/status.svg": { "/badges/{repo_id}/status.svg": {
"get": { "get": {
"produces": [ "produces": [
"image/svg+xml" "image/svg+xml"
@ -294,16 +294,9 @@ const docTemplate = `{
"summary": "Get status badge, SVG format", "summary": "Get status badge, SVG format",
"parameters": [ "parameters": [
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
} }
@ -751,7 +744,7 @@ const docTemplate = `{
} }
} }
}, },
"/logs/{owner}/{name}/{pipeline}/{stepID}": { "/logs/{repo_id}/{pipeline}/{stepID}": {
"get": { "get": {
"produces": [ "produces": [
"text/plain" "text/plain"
@ -762,16 +755,9 @@ const docTemplate = `{
"summary": "Log stream", "summary": "Log stream",
"parameters": [ "parameters": [
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "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": { "get": {
"produces": [ "produces": [
"application/json" "application/json"
@ -1225,16 +1247,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
} }
@ -1266,16 +1281,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
} }
@ -1307,16 +1315,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
} }
@ -1348,16 +1349,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
}, },
@ -1381,7 +1375,7 @@ const docTemplate = `{
} }
} }
}, },
"/repos/{owner}/{name}/branches": { "/repos/{repo_id}/branches": {
"get": { "get": {
"produces": [ "produces": [
"application/json" "application/json"
@ -1400,16 +1394,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
}, },
@ -1441,7 +1428,7 @@ const docTemplate = `{
} }
} }
}, },
"/repos/{owner}/{name}/chown": { "/repos/{repo_id}/chown": {
"post": { "post": {
"produces": [ "produces": [
"application/json" "application/json"
@ -1460,16 +1447,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
} }
@ -1484,7 +1464,7 @@ const docTemplate = `{
} }
} }
}, },
"/repos/{owner}/{name}/cron": { "/repos/{repo_id}/cron": {
"get": { "get": {
"produces": [ "produces": [
"application/json" "application/json"
@ -1503,16 +1483,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
}, },
@ -1561,16 +1534,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
}, },
@ -1594,7 +1560,7 @@ const docTemplate = `{
} }
} }
}, },
"/repos/{owner}/{name}/cron/{cron}": { "/repos/{repo_id}/cron/{cron}": {
"get": { "get": {
"produces": [ "produces": [
"application/json" "application/json"
@ -1613,16 +1579,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
}, },
@ -1661,16 +1620,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
}, },
@ -1709,16 +1661,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
}, },
@ -1754,16 +1699,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
}, },
@ -1794,7 +1732,7 @@ const docTemplate = `{
} }
} }
}, },
"/repos/{owner}/{name}/logs/{number}": { "/repos/{repo_id}/logs/{number}": {
"post": { "post": {
"produces": [ "produces": [
"text/plain" "text/plain"
@ -1813,16 +1751,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
}, },
@ -1841,7 +1772,7 @@ const docTemplate = `{
} }
} }
}, },
"/repos/{owner}/{name}/logs/{number}/{stepID}": { "/repos/{repo_id}/logs/{number}/{stepID}": {
"get": { "get": {
"produces": [ "produces": [
"application/json" "application/json"
@ -1860,16 +1791,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
}, },
@ -1901,7 +1825,7 @@ const docTemplate = `{
} }
} }
}, },
"/repos/{owner}/{name}/move": { "/repos/{repo_id}/move": {
"post": { "post": {
"produces": [ "produces": [
"text/plain" "text/plain"
@ -1920,16 +1844,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
}, },
@ -1948,7 +1865,7 @@ const docTemplate = `{
} }
} }
}, },
"/repos/{owner}/{name}/permissions": { "/repos/{repo_id}/permissions": {
"get": { "get": {
"description": "The repository permission, according to the used access token.", "description": "The repository permission, according to the used access token.",
"produces": [ "produces": [
@ -1992,7 +1909,7 @@ const docTemplate = `{
} }
} }
}, },
"/repos/{owner}/{name}/pipelines": { "/repos/{repo_id}/pipelines": {
"get": { "get": {
"produces": [ "produces": [
"application/json" "application/json"
@ -2011,16 +1928,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
}, },
@ -2069,16 +1979,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
}, },
@ -2102,7 +2005,7 @@ const docTemplate = `{
} }
} }
}, },
"/repos/{owner}/{name}/pipelines/{number}": { "/repos/{repo_id}/pipelines/{number}": {
"get": { "get": {
"produces": [ "produces": [
"application/json" "application/json"
@ -2121,16 +2024,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
}, },
@ -2213,7 +2109,7 @@ const docTemplate = `{
} }
} }
}, },
"/repos/{owner}/{name}/pipelines/{number}/approve": { "/repos/{repo_id}/pipelines/{number}/approve": {
"post": { "post": {
"produces": [ "produces": [
"application/json" "application/json"
@ -2232,16 +2128,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
}, },
@ -2263,7 +2152,7 @@ const docTemplate = `{
} }
} }
}, },
"/repos/{owner}/{name}/pipelines/{number}/cancel": { "/repos/{repo_id}/pipelines/{number}/cancel": {
"post": { "post": {
"produces": [ "produces": [
"text/plain" "text/plain"
@ -2282,16 +2171,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
}, },
@ -2310,7 +2192,7 @@ const docTemplate = `{
} }
} }
}, },
"/repos/{owner}/{name}/pipelines/{number}/config": { "/repos/{repo_id}/pipelines/{number}/config": {
"get": { "get": {
"produces": [ "produces": [
"application/json" "application/json"
@ -2329,16 +2211,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
}, },
@ -2363,7 +2238,7 @@ const docTemplate = `{
} }
} }
}, },
"/repos/{owner}/{name}/pipelines/{number}/decline": { "/repos/{repo_id}/pipelines/{number}/decline": {
"post": { "post": {
"produces": [ "produces": [
"application/json" "application/json"
@ -2382,16 +2257,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
}, },
@ -2413,7 +2281,7 @@ const docTemplate = `{
} }
} }
}, },
"/repos/{owner}/{name}/pull_requests": { "/repos/{repo_id}/pull_requests": {
"get": { "get": {
"produces": [ "produces": [
"application/json" "application/json"
@ -2432,16 +2300,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
}, },
@ -2473,7 +2334,7 @@ const docTemplate = `{
} }
} }
}, },
"/repos/{owner}/{name}/registry": { "/repos/{repo_id}/registry": {
"get": { "get": {
"produces": [ "produces": [
"application/json" "application/json"
@ -2492,16 +2353,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
}, },
@ -2550,16 +2404,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
}, },
@ -2583,7 +2430,7 @@ const docTemplate = `{
} }
} }
}, },
"/repos/{owner}/{name}/registry/{registry}": { "/repos/{repo_id}/registry/{registry}": {
"get": { "get": {
"produces": [ "produces": [
"application/json" "application/json"
@ -2602,16 +2449,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
}, },
@ -2650,16 +2490,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
}, },
@ -2695,16 +2528,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
}, },
@ -2735,7 +2561,7 @@ const docTemplate = `{
} }
} }
}, },
"/repos/{owner}/{name}/repair": { "/repos/{repo_id}/repair": {
"post": { "post": {
"produces": [ "produces": [
"text/plain" "text/plain"
@ -2754,16 +2580,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
} }
@ -2775,7 +2594,7 @@ const docTemplate = `{
} }
} }
}, },
"/repos/{owner}/{name}/secrets": { "/repos/{repo_id}/secrets": {
"get": { "get": {
"produces": [ "produces": [
"application/json" "application/json"
@ -2794,16 +2613,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
}, },
@ -2852,16 +2664,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
}, },
@ -2885,7 +2690,7 @@ const docTemplate = `{
} }
} }
}, },
"/repos/{owner}/{name}/secrets/{secretName}": { "/repos/{repo_id}/secrets/{secretName}": {
"get": { "get": {
"produces": [ "produces": [
"application/json" "application/json"
@ -2904,16 +2709,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
}, },
@ -2952,16 +2750,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
}, },
@ -2997,16 +2788,9 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"type": "string", "type": "integer",
"description": "the repository owner's name", "description": "the repository id",
"name": "owner", "name": "repo_id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the repository name",
"name": "name",
"in": "path", "in": "path",
"required": true "required": true
}, },
@ -3753,24 +3537,15 @@ const docTemplate = `{
"finished_at": { "finished_at": {
"type": "integer" "type": "integer"
}, },
"full_name": {
"type": "string"
},
"id": { "id": {
"type": "integer" "type": "integer"
}, },
"message": { "message": {
"type": "string" "type": "string"
}, },
"name": {
"type": "string"
},
"number": { "number": {
"type": "integer" "type": "integer"
}, },
"owner": {
"type": "string"
},
"ref": { "ref": {
"type": "string" "type": "string"
}, },
@ -3780,6 +3555,9 @@ const docTemplate = `{
"remote": { "remote": {
"type": "string" "type": "string"
}, },
"repo_id": {
"type": "integer"
},
"started_at": { "started_at": {
"type": "integer" "type": "integer"
}, },
@ -4037,6 +3815,10 @@ const docTemplate = `{
"default_branch": { "default_branch": {
"type": "string" "type": "string"
}, },
"forge_remote_id": {
"description": "ForgeRemoteID is the unique identifier for the repository on the forge.",
"type": "string"
},
"full_name": { "full_name": {
"type": "string" "type": "string"
}, },

View file

@ -5,14 +5,14 @@ Woodpecker has integrated support for repository status badges. These badges can
## Badge endpoint ## Badge endpoint
```text ```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. 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 ```diff
-<scheme>://<hostname>/api/badges/<owner>/<repo>/status.svg -<scheme>://<hostname>/api/badges/<repo-id>/status.svg
+<scheme>://<hostname>/api/badges/<owner>/<repo>/status.svg?branch=<branch> +<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. 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). - 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` - `/api/queue/resume` & `/api/queue/pause` endpoint methods were changed from `GET` to `POST`
- rename `pipeline:` key in your workflow config to `steps:` - 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 ## 0.15.0

View file

@ -22,6 +22,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
"strconv"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@ -37,15 +38,29 @@ import (
// GetBadge // GetBadge
// //
// @Summary Get status badge, SVG format // @Summary Get status badge, SVG format
// @Router /badges/{owner}/{name}/status.svg [get] // @Router /badges/{repo_id}/status.svg [get]
// @Produce image/svg+xml // @Produce image/svg+xml
// @Success 200 // @Success 200
// @Tags Badges // @Tags Badges
// @Param owner path string true "the repository owner's name" // @Param repo_id path int true "the repository id"
// @Param name path string true "the repository name"
func GetBadge(c *gin.Context) { func GetBadge(c *gin.Context) {
_store := store.FromContext(c) _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 || !repo.IsActive {
if err == nil || errors.Is(err, types.RecordNotExist) { if err == nil || errors.Is(err, types.RecordNotExist) {
c.AbortWithStatus(http.StatusNotFound) 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 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 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/ // @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 // @Produce xml
// @Success 200 // @Success 200
// @Tags Badges // @Tags Badges

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -96,9 +96,9 @@ func GetRepos(c *gin.Context) {
} }
if all { if all {
active := map[string]bool{} active := map[model.ForgeRemoteID]*model.Repo{}
for _, r := range activeRepos { for _, r := range activeRepos {
active[r.FullName] = r.IsActive active[r.ForgeRemoteID] = r
} }
_repos, err := _forge.Repos(c, user) _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) c.String(http.StatusInternalServerError, "Error fetching repository list. %s", err)
return return
} }
var repos []*model.Repo var repos []*model.Repo
for _, r := range _repos { for _, r := range _repos {
if r.Perm.Push { if r.Perm.Push {
if active[r.FullName] { if active[r.ForgeRemoteID] != nil && active[r.ForgeRemoteID].IsActive {
r.IsActive = true 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 { func GetPipelineStatusLink(repo *model.Repo, pipeline *model.Pipeline, step *model.Step) string {
if step == nil { 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 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) repos = append(repos, repo)
} }

View file

@ -17,10 +17,7 @@ package model
// Feed represents an item in the user's feed or timeline. // Feed represents an item in the user's feed or timeline.
type Feed struct { type Feed struct {
Owner string `json:"owner" xorm:"feed_repo_owner"` RepoID int64 `json:"repo_id" xorm:"feed_repo_id"`
Name string `json:"name" xorm:"feed_repo_name"`
FullName string `json:"full_name" xorm:"feed_repo_full_name"`
ID int64 `json:"id,omitempty" xorm:"feed_pipeline_id"` ID int64 `json:"id,omitempty" xorm:"feed_pipeline_id"`
Number int64 `json:"number,omitempty" xorm:"feed_pipeline_number"` Number int64 `json:"number,omitempty" xorm:"feed_pipeline_number"`
Event string `json:"event,omitempty" xorm:"feed_pipeline_event"` 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'"` ID int64 `json:"id,omitempty" xorm:"pk autoincr 'repo_id'"`
UserID int64 `json:"-" xorm:"repo_user_id"` UserID int64 `json:"-" xorm:"repo_user_id"`
// ForgeRemoteID is the unique identifier for the repository on the forge. // 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'"` Owner string `json:"owner" xorm:"UNIQUE(name) 'repo_owner'"`
Name string `json:"name" xorm:"UNIQUE(name) 'repo_name'"` Name string `json:"name" xorm:"UNIQUE(name) 'repo_name'"`
FullName string `json:"full_name" xorm:"UNIQUE 'repo_full_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) apiBase.GET("/repos/lookup/*repo_full_name", api.LookupRepo) // TODO: check if this public route is a security issue
repoBase := apiBase.Group("/repos/:owner/:name") apiBase.POST("/repos", session.MustUser(), api.PostRepo)
repoBase := apiBase.Group("/repos/:repo_id")
{ {
repoBase.Use(session.SetRepo()) repoBase.Use(session.SetRepo())
repoBase.Use(session.SetPerm()) 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("/status.svg", api.GetBadge)
badges.GET("/cc.xml", api.GetCC) 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 := apiBase.Group("/pipelines")
{ {
pipelines.Use(session.MustAdmin()) pipelines.Use(session.MustAdmin())

View file

@ -17,6 +17,7 @@ package session
import ( import (
"errors" "errors"
"net/http" "net/http"
"strconv"
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -44,14 +45,28 @@ func Repo(c *gin.Context) *model.Repo {
func SetRepo() gin.HandlerFunc { func SetRepo() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
var ( var (
_store = store.FromContext(c) _store = store.FromContext(c)
owner = c.Param("owner") owner = c.Param("owner")
name = c.Param("name") name = c.Param("name")
user = User(c) _repoID = c.Param("repo_id")
user = User(c)
) )
repo, err := _store.GetRepoName(owner + "/" + name) var repo *model.Repo
if err == nil { 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.Set("repo", repo)
c.Next() c.Next()
return return
@ -64,15 +79,17 @@ func SetRepo() gin.HandlerFunc {
err.Error(), err.Error(),
) )
if user != nil { if user == nil {
if errors.Is(err, types.RecordNotExist) {
c.AbortWithStatus(http.StatusNotFound)
return
}
_ = c.AbortWithError(http.StatusInternalServerError, err)
} else {
c.AbortWithStatus(http.StatusUnauthorized) 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" "github.com/woodpecker-ci/woodpecker/server/model"
) )
var feedItemSelect = `repos.repo_owner as feed_repo_owner, var feedItemSelect = `repos.repo_id as feed_repo_id,
repos.repo_name as feed_repo_name,
repos.repo_full_name as feed_repo_full_name,
pipelines.pipeline_id as feed_pipeline_id, pipelines.pipeline_id as feed_pipeline_id,
pipelines.pipeline_number as feed_pipeline_number, pipelines.pipeline_number as feed_pipeline_number,
pipelines.pipeline_event as feed_pipeline_event, pipelines.pipeline_event as feed_pipeline_event,

View file

@ -126,6 +126,7 @@ func TestRepoListLatest(t *testing.T) {
assert.NoError(t, store.CreateUser(user)) assert.NoError(t, store.CreateUser(user))
repo1 := &model.Repo{ repo1 := &model.Repo{
ID: 1,
Owner: "bradrydzewski", Owner: "bradrydzewski",
Name: "test", Name: "test",
FullName: "bradrydzewski/test", FullName: "bradrydzewski/test",
@ -133,6 +134,7 @@ func TestRepoListLatest(t *testing.T) {
IsActive: true, IsActive: true,
} }
repo2 := &model.Repo{ repo2 := &model.Repo{
ID: 2,
Owner: "test", Owner: "test",
Name: "test", Name: "test",
FullName: "test/test", FullName: "test/test",
@ -140,6 +142,7 @@ func TestRepoListLatest(t *testing.T) {
IsActive: true, IsActive: true,
} }
repo3 := &model.Repo{ repo3 := &model.Repo{
ID: 3,
Owner: "octocat", Owner: "octocat",
Name: "hello-world", Name: "hello-world",
FullName: "octocat/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 { if got, want := pipelines[0].Status, string(model.StatusRunning); want != got {
t.Errorf("Want repository status %s, got %s", want, got) t.Errorf("Want repository status %s, got %s", want, got)
} }
if got, want := pipelines[0].FullName, repo1.FullName; want != got { if got, want := pipelines[0].RepoID, repo1.ID; want != got {
t.Errorf("Want repository name %s, got %s", want, got) t.Errorf("Want repository id %d, got %d", want, got)
} }
if got, want := pipelines[1].Status, string(model.StatusKilled); want != got { if got, want := pipelines[1].Status, string(model.StatusKilled); want != got {
t.Errorf("Want repository status %s, got %s", want, got) t.Errorf("Want repository status %s, got %s", want, got)
} }
if got, want := pipelines[1].FullName, repo2.FullName; want != got { if got, want := pipelines[1].RepoID, repo2.ID; want != got {
t.Errorf("Want repository name %s, got %s", 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) { 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). return pipelines, s.paginate(p).Where("pipeline_repo_id = ?", repo.ID).
Desc("pipeline_number"). Desc("pipeline_number").
Find(&pipelines) Find(&pipelines)

View file

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

View file

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

View file

@ -80,7 +80,7 @@ const newPipelineVariable = ref<{ name: string; value: string }>({ name: '', val
const loading = ref(true); const loading = ref(true);
onMounted(async () => { 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) => ({ branches.value = data.map((e) => ({
text: e, text: e,
value: e, value: e,
@ -103,7 +103,7 @@ function deleteVar(key: string) {
async function triggerManualPipeline() { async function triggerManualPipeline() {
loading.value = true; 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'); emit('close');

View file

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

View file

@ -1,43 +1,29 @@
<template> <template>
<aside <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" 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')" :aria-label="$t('pipeline_feed')"
> >
<router-link <router-link
v-for="pipeline in sortedPipelineFeed" v-for="pipeline in sortedPipelines"
:key="pipeline.id" :key="pipeline.id"
:to="{ :to="{
name: 'repo-pipeline', 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" 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" /> <PipelineFeedItem :pipeline="pipeline" />
</router-link> </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> </aside>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent } from 'vue';
import PipelineFeedItem from '~/components/pipeline-feed/PipelineFeedItem.vue'; import PipelineFeedItem from '~/components/pipeline-feed/PipelineFeedItem.vue';
import usePipelineFeed from '~/compositions/usePipelineFeed'; import usePipelineFeed from '~/compositions/usePipelineFeed';
export default defineComponent({ const pipelineFeed = usePipelineFeed();
name: 'PipelineFeedSidebar', const { isOpen, sortedPipelines } = pipelineFeed;
components: { PipelineFeedItem },
setup() {
const pipelineFeed = usePipelineFeed();
return {
isPipelineFeedOpen: pipelineFeed.isOpen,
sortedPipelineFeed: pipelineFeed.sortedPipelines,
};
},
});
</script> </script>

View file

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

View file

@ -190,7 +190,7 @@ async function download() {
let logs; let logs;
try { try {
downloadInProgress.value = true; 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) { } catch (e) {
notifications.notifyError(e, i18n.t('repo.pipeline.log_download_error')); notifications.notifyError(e, i18n.t('repo.pipeline.log_download_error'));
return; return;
@ -239,22 +239,16 @@ async function loadLogs() {
} }
if (isStepFinished(step.value)) { 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 })); logs?.forEach((line) => writeLog({ index: line.line, text: atob(line.data), time: line.time }));
flushLogs(false); flushLogs(false);
} }
if (isStepRunning(step.value)) { if (isStepRunning(step.value)) {
stream.value = apiClient.streamLogs( stream.value = apiClient.streamLogs(repo.value.id, pipeline.value.number, step.value.id, (line) => {
repo.value.owner, writeLog({ index: line.line, text: atob(line.data), time: line.time });
repo.value.name, flushLogs(true);
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> </Panel>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent, inject, Ref } from 'vue'; import { computed, inject, Ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
@ -57,74 +57,56 @@ import { useAsyncAction } from '~/compositions/useAsyncAction';
import useNotifications from '~/compositions/useNotifications'; import useNotifications from '~/compositions/useNotifications';
import { Repo } from '~/lib/api/types'; import { Repo } from '~/lib/api/types';
export default defineComponent({ const apiClient = useApiClient();
name: 'ActionsTab', const router = useRouter();
const notifications = useNotifications();
const i18n = useI18n();
components: { Button, Panel }, const repo = inject<Ref<Repo>>('repo');
setup() { const { doSubmit: repairRepo, isLoading: isRepairingRepo } = useAsyncAction(async () => {
const apiClient = useApiClient(); if (!repo) {
const router = useRouter(); throw new Error('Unexpected: Repo should be set');
const notifications = useNotifications(); }
const i18n = useI18n();
const repo = inject<Ref<Repo>>('repo'); await apiClient.repairRepo(repo.value.id);
notifications.notify({ title: i18n.t('repo.settings.actions.repair.success'), type: 'success' });
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,
};
},
}); });
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> </script>

View file

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

View file

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

View file

@ -148,7 +148,7 @@ export default defineComponent({
throw new Error('Unexpected: Repo should be set'); throw new Error('Unexpected: Repo should be set');
} }
await repoStore.loadRepo(repo.value.owner, repo.value.name); await repoStore.loadRepo(repo.value.id);
loadRepoSettings(); loadRepoSettings();
} }
@ -161,7 +161,7 @@ export default defineComponent({
throw new Error('Unexpected: Repo-Settings should be set'); 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(); await loadRepo();
notifications.notify({ title: i18n.t('repo.settings.general.success'), type: 'success' }); 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"); 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); const { resetPage, data: registries } = usePagination(loadRegistries, () => !selectedRegistry.value);
@ -139,9 +139,9 @@ export default defineComponent({
} }
if (isEditingRegistry.value) { if (isEditingRegistry.value) {
await apiClient.updateRegistry(repo.value.owner, repo.value.name, selectedRegistry.value); await apiClient.updateRegistry(repo.value.id, selectedRegistry.value);
} else { } else {
await apiClient.createRegistry(repo.value.owner, repo.value.name, selectedRegistry.value); await apiClient.createRegistry(repo.value.id, selectedRegistry.value);
} }
notifications.notify({ notifications.notify({
title: i18n.t( title: i18n.t(
@ -159,7 +159,7 @@ export default defineComponent({
} }
const registryAddress = encodeURIComponent(_registry.address); 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' }); notifications.notify({ title: i18n.t('repo.settings.registries.deleted'), type: 'success' });
resetPage(); resetPage();
}); });

View file

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

View file

@ -28,13 +28,13 @@ export default () => {
return; return;
} }
const { pipeline } = data; const { pipeline } = data;
pipelineStore.setPipeline(repo.owner, repo.name, pipeline); pipelineStore.setPipeline(repo.id, pipeline);
// contains step update // contains step update
if (!data.step) { if (!data.step) {
return; return;
} }
const { step } = data; 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) { export function useRouteBackOrDefault(to: RouteLocationRaw) {
const router = useRouter(); const router = useRouter();
return () => { return async () => {
// TODO: use history navigation once we have found a solution for filtering external history entries if ((window.history.state as { back: string }).back === null) {
// if (window.history.length > 2) { await router.replace(to);
// router.back(); return;
// } else { }
router.replace(to); router.back();
// }
}; };
} }

View file

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

View file

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

View file

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

View file

@ -12,31 +12,108 @@ const routes: RouteRecordRaw[] = [
}, },
{ {
path: '/repos', path: '/repos',
name: 'repos', component: (): Component => import('~/views/RouterView.vue'),
component: (): Component => import('~/views/Repos.vue'), children: [
meta: { authentication: 'required' }, {
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', path: '/org/:orgName',
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',
component: (): Component => import('~/views/org/OrgWrapper.vue'), component: (): Component => import('~/views/org/OrgWrapper.vue'),
props: true, props: true,
children: [ children: [
{ {
path: '', path: '',
name: 'org', name: 'org',
redirect: (route) => ({ name: 'repos-owner', params: route.params }), component: (): Component => import('~/views/org/OrgRepos.vue'),
props: true,
}, },
{ {
path: 'settings', path: 'settings',
@ -48,114 +125,25 @@ const routes: RouteRecordRaw[] = [
], ],
}, },
{ {
path: '/:repoOwner/:repoName', path: '/admin',
name: 'repo-wrapper', component: (): Component => import('~/views/RouterView.vue'),
component: (): Component => import('~/views/repo/RepoWrapper.vue'), meta: { authentication: 'required' },
props: true,
children: [ children: [
{ {
path: '', path: '',
name: 'repo', name: 'admin',
component: (): Component => import('~/views/repo/RepoPipelines.vue'), component: (): Component => import('~/views/admin/Admin.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'),
props: true, 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', path: 'settings',
name: 'repo-settings', name: 'admin-settings',
component: (): Component => import('~/views/repo/RepoSettings.vue'), component: (): Component => import('~/views/admin/AdminSettings.vue'),
meta: { authentication: 'required' },
props: true, 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', path: '/user',
name: 'user', name: 'user',
@ -177,6 +165,19 @@ const routes: RouteRecordRaw[] = [
meta: { blank: true }, meta: { blank: true },
props: 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(.*)*', path: '/:pathMatch(.*)*',
name: 'not-found', name: 'not-found',
@ -198,7 +199,8 @@ router.beforeEach(async (to, _, next) => {
} }
const authentication = useAuthentication(); 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 } }); next({ name: 'login', query: { url: to.fullPath } });
return; return;
} }

View file

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

View file

@ -9,7 +9,7 @@
v-for="repo in searchedRepos" v-for="repo in searchedRepos"
:key="repo.id" :key="repo.id"
class="items-center" 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 class="text-color">{{ repo.full_name }}</span>
<span v-if="repo.active" class="ml-auto text-color-alt">{{ $t('repo.enable.enabled') }}</span> <span v-if="repo.active" class="ml-auto text-color-alt">{{ $t('repo.enable.enabled') }}</span>
@ -25,8 +25,8 @@
</Scaffold> </Scaffold>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent, onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
@ -40,48 +40,27 @@ import { useRepoSearch } from '~/compositions/useRepoSearch';
import { useRouteBackOrDefault } from '~/compositions/useRouteBackOrDefault'; import { useRouteBackOrDefault } from '~/compositions/useRouteBackOrDefault';
import { Repo } from '~/lib/api/types'; import { Repo } from '~/lib/api/types';
export default defineComponent({ const router = useRouter();
name: 'RepoAdd', const apiClient = useApiClient();
const notifications = useNotifications();
const repos = ref<Repo[]>();
const repoToActivate = ref<Repo>();
const search = ref('');
const i18n = useI18n();
components: { const { searchedRepos } = useRepoSearch(repos, search);
Button,
ListItem,
Scaffold,
},
setup() { onMounted(async () => {
const router = useRouter(); repos.value = await apiClient.getRepoList({ all: true });
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,
};
},
}); });
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> </script>

View file

@ -9,11 +9,7 @@
</template> </template>
<div class="space-y-4"> <div class="space-y-4">
<ListItem <ListItem v-for="repo in searchedRepos" :key="repo.id" :to="{ name: 'repo', params: { repoId: repo.id } }">
v-for="repo in searchedRepos"
:key="repo.id"
:to="{ name: 'repo', params: { repoName: repo.name, repoOwner: repo.owner } }"
>
<span class="text-color">{{ `${repo.owner} / ${repo.name}` }}</span> <span class="text-color">{{ `${repo.owner} / ${repo.name}` }}</span>
</ListItem> </ListItem>
</div> </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"> <Scaffold enable-tabs :go-back="goBack">
<template #title> <template #title>
<span> <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 }} {{ org.name }}
</router-link> </router-link>
/ /
@ -16,8 +16,8 @@
</Scaffold> </Scaffold>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent, inject, onMounted, Ref } from 'vue'; import { inject, onMounted, Ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
@ -27,40 +27,26 @@ import useNotifications from '~/compositions/useNotifications';
import { useRouteBackOrDefault } from '~/compositions/useRouteBackOrDefault'; import { useRouteBackOrDefault } from '~/compositions/useRouteBackOrDefault';
import { Org, OrgPermissions } from '~/lib/api/types'; import { Org, OrgPermissions } from '~/lib/api/types';
export default defineComponent({ const notifications = useNotifications();
name: 'OrgSettings', const router = useRouter();
const i18n = useI18n();
components: { const orgPermissions = inject<Ref<OrgPermissions>>('org-permissions');
Tab, if (!orgPermissions) {
OrgSecretsTab, throw new Error('Unexpected: "orgPermissions" should be provided at this place');
}, }
setup() { const org = inject<Ref<Org>>('org');
const notifications = useNotifications(); if (!org) {
const router = useRouter(); throw new Error('Unexpected: "org" should be provided at this place');
const i18n = useI18n(); }
const orgPermissions = inject<Ref<OrgPermissions>>('org-permissions'); onMounted(async () => {
if (!orgPermissions) { if (!orgPermissions.value.admin) {
throw new Error('Unexpected: "orgPermissions" should be provided at this place'); notifications.notify({ type: 'error', title: i18n.t('org.settings.not_allowed') });
} await router.replace({ name: 'home' });
}
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' }),
};
},
}); });
const goBack = useRouteBackOrDefault({ name: 'repos-owner' });
</script> </script>

View file

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

View file

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

View file

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

View file

@ -133,7 +133,7 @@ const { doSubmit: approvePipeline, isLoading: isApprovingPipeline } = useAsyncAc
throw new Error('Unexpected: Repo is undefined'); 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' }); 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'); 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' }); notifications.notify({ title: i18n.t('repo.pipeline.protected.decline_success'), type: 'success' });
}); });
</script> </script>

View file

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

View file

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

View file

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

View file

@ -45,7 +45,10 @@ type Client interface {
UserDel(string) error UserDel(string) error
// Repo returns a repository by name. // 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 // RepoList returns a list of all repositories to which the user has explicit
// access in the host system. // access in the host system.
@ -56,94 +59,94 @@ type Client interface {
RepoListOpts(bool, bool) ([]*Repo, error) RepoListOpts(bool, bool) ([]*Repo, error)
// RepoPost activates a repository. // RepoPost activates a repository.
RepoPost(string, string) (*Repo, error) RepoPost(forgeRemoteID int64) (*Repo, error)
// RepoPatch updates a repository. // RepoPatch updates a repository.
RepoPatch(string, string, *RepoPatch) (*Repo, error) RepoPatch(repoID int64, repo *RepoPatch) (*Repo, error)
// RepoMove moves the repository // RepoMove moves the repository
RepoMove(string, string, string) error RepoMove(repoID int64, dst string) error
// RepoChown updates a repository owner. // RepoChown updates a repository owner.
RepoChown(string, string) (*Repo, error) RepoChown(repoID int64) (*Repo, error)
// RepoRepair repairs the repository hooks. // RepoRepair repairs the repository hooks.
RepoRepair(string, string) error RepoRepair(repoID int64) error
// RepoDel deletes a repository. // RepoDel deletes a repository.
RepoDel(string, string) error RepoDel(repoID int64) error
// Pipeline returns a repository pipeline by number. // 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 // PipelineLast returns the latest repository pipeline by branch. An empty branch
// will result in the default 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 // PipelineList returns a list of recent pipelines for the
// the specified repository. // the specified repository.
PipelineList(string, string) ([]*Pipeline, error) PipelineList(repoID int64) ([]*Pipeline, error)
// PipelineQueue returns a list of enqueued pipelines. // PipelineQueue returns a list of enqueued pipelines.
PipelineQueue() ([]*Activity, error) PipelineQueue() ([]*Activity, error)
// PipelineCreate returns creates a pipeline on specified branch. // 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 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 stops the given pipeline.
PipelineStop(string, string, int) error PipelineStop(repoID int64, pipeline int) error
// PipelineApprove approves a blocked pipeline. // PipelineApprove approves a blocked pipeline.
PipelineApprove(string, string, int) (*Pipeline, error) PipelineApprove(repoID int64, pipeline int) (*Pipeline, error)
// PipelineDecline declines a blocked pipeline. // 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 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 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 // Deploy triggers a deployment for an existing pipeline using the specified
// target environment. // 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 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 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 returns a list of all repository registries.
RegistryList(owner, name string) ([]*Registry, error) RegistryList(repoID int64) ([]*Registry, error)
// RegistryCreate creates a registry. // RegistryCreate creates a registry.
RegistryCreate(owner, name string, registry *Registry) (*Registry, error) RegistryCreate(repoID int64, registry *Registry) (*Registry, error)
// RegistryUpdate updates a registry. // RegistryUpdate updates a registry.
RegistryUpdate(owner, name string, registry *Registry) (*Registry, error) RegistryUpdate(repoID int64, registry *Registry) (*Registry, error)
// RegistryDelete deletes a registry. // RegistryDelete deletes a registry.
RegistryDelete(owner, name, hostname string) error RegistryDelete(repoID int64, hostname string) error
// Secret returns a secret by name. // 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 returns a list of all repository secrets.
SecretList(owner, name string) ([]*Secret, error) SecretList(repoID int64) ([]*Secret, error)
// SecretCreate creates a secret. // SecretCreate creates a secret.
SecretCreate(owner, name string, secret *Secret) (*Secret, error) SecretCreate(repoID int64, secret *Secret) (*Secret, error)
// SecretUpdate updates a secret. // SecretUpdate updates a secret.
SecretUpdate(owner, name string, secret *Secret) (*Secret, error) SecretUpdate(repoID int64, secret *Secret) (*Secret, error)
// SecretDelete deletes a secret. // SecretDelete deletes a secret.
SecretDelete(owner, name, secret string) error SecretDelete(repoID int64, secret string) error
// OrgSecret returns an organization secret by name. // OrgSecret returns an organization secret by name.
OrgSecret(owner, secret string) (*Secret, error) OrgSecret(owner, secret string) (*Secret, error)
@ -185,19 +188,19 @@ type Client interface {
SetLogLevel(logLevel *LogLevel) (*LogLevel, error) SetLogLevel(logLevel *LogLevel) (*LogLevel, error)
// CronList list all cron jobs of a repo // 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 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 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 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 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 returns a list of all registered agents
AgentList() ([]*Agent, error) AgentList() ([]*Agent, error)

View file

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