mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2025-08-12 03:54:02 +00:00
Migrate repo output format to customizable output (#4888)
This commit is contained in:
parent
a1dbcc90a5
commit
9a2b13341e
@ -31,7 +31,7 @@ var userListCmd = &cli.Command{
|
|||||||
Usage: "list all users",
|
Usage: "list all users",
|
||||||
ArgsUsage: " ",
|
ArgsUsage: " ",
|
||||||
Action: userList,
|
Action: userList,
|
||||||
Flags: []cli.Flag{common.FormatFlag(tmplUserList)},
|
Flags: []cli.Flag{common.FormatFlag(tmplUserList, false)},
|
||||||
}
|
}
|
||||||
|
|
||||||
func userList(ctx context.Context, c *cli.Command) error {
|
func userList(ctx context.Context, c *cli.Command) error {
|
||||||
|
@ -31,7 +31,7 @@ var userShowCmd = &cli.Command{
|
|||||||
Usage: "show user information",
|
Usage: "show user information",
|
||||||
ArgsUsage: "<username>",
|
ArgsUsage: "<username>",
|
||||||
Action: userShow,
|
Action: userShow,
|
||||||
Flags: []cli.Flag{common.FormatFlag(tmplUserInfo)},
|
Flags: []cli.Flag{common.FormatFlag(tmplUserInfo, false)},
|
||||||
}
|
}
|
||||||
|
|
||||||
func userShow(ctx context.Context, c *cli.Command) error {
|
func userShow(ctx context.Context, c *cli.Command) error {
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
|
|
||||||
"go.woodpecker-ci.org/woodpecker/v3/shared/logger"
|
"go.woodpecker-ci.org/woodpecker/v3/shared/logger"
|
||||||
@ -63,10 +65,15 @@ var GlobalFlags = append([]cli.Flag{
|
|||||||
|
|
||||||
// FormatFlag return format flag with value set based on template
|
// FormatFlag return format flag with value set based on template
|
||||||
// if hidden value is set, flag will be hidden.
|
// if hidden value is set, flag will be hidden.
|
||||||
func FormatFlag(tmpl string, hidden ...bool) *cli.StringFlag {
|
func FormatFlag(tmpl string, deprecated bool, hidden ...bool) *cli.StringFlag {
|
||||||
|
usage := "format output"
|
||||||
|
if deprecated {
|
||||||
|
usage = fmt.Sprintf("%s (deprecated)", usage)
|
||||||
|
}
|
||||||
|
|
||||||
return &cli.StringFlag{
|
return &cli.StringFlag{
|
||||||
Name: "format",
|
Name: "format",
|
||||||
Usage: "format output",
|
Usage: usage,
|
||||||
Value: tmpl,
|
Value: tmpl,
|
||||||
Hidden: len(hidden) != 0,
|
Hidden: len(hidden) != 0,
|
||||||
}
|
}
|
||||||
|
@ -52,15 +52,15 @@ func (o *Table) Columns() (cols []string) {
|
|||||||
|
|
||||||
// AddFieldAlias overrides the field name to allow custom column headers.
|
// AddFieldAlias overrides the field name to allow custom column headers.
|
||||||
func (o *Table) AddFieldAlias(field, alias string) *Table {
|
func (o *Table) AddFieldAlias(field, alias string) *Table {
|
||||||
o.fieldAlias[field] = alias
|
o.fieldAlias[strings.ToLower(alias)] = field
|
||||||
return o
|
return o
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddFieldFn adds a function which handles the output of the specified field.
|
// AddFieldFn adds a function which handles the output of the specified field.
|
||||||
func (o *Table) AddFieldFn(field string, fn FieldFn) *Table {
|
func (o *Table) AddFieldFn(field string, fn FieldFn) *Table {
|
||||||
o.fieldMapping[field] = fn
|
o.fieldMapping[strings.ToLower(field)] = fn
|
||||||
o.allowedFields[field] = true
|
o.allowedFields[strings.ToLower(field)] = true
|
||||||
o.columns[field] = true
|
o.columns[strings.ToLower(field)] = true
|
||||||
return o
|
return o
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,9 +117,6 @@ func (o *Table) ValidateColumns(cols []string) error {
|
|||||||
func (o *Table) WriteHeader(columns []string) {
|
func (o *Table) WriteHeader(columns []string) {
|
||||||
var header []string
|
var header []string
|
||||||
for _, col := range columns {
|
for _, col := range columns {
|
||||||
if alias, ok := o.fieldAlias[col]; ok {
|
|
||||||
col = alias
|
|
||||||
}
|
|
||||||
header = append(header, strings.ReplaceAll(strings.ToUpper(col), "_", " "))
|
header = append(header, strings.ReplaceAll(strings.ToUpper(col), "_", " "))
|
||||||
}
|
}
|
||||||
_, _ = fmt.Fprintln(o.w, strings.Join(header, "\t"))
|
_, _ = fmt.Fprintln(o.w, strings.Join(header, "\t"))
|
||||||
@ -146,12 +143,9 @@ func (o *Table) Write(columns []string, obj any) error {
|
|||||||
for _, col := range columns {
|
for _, col := range columns {
|
||||||
colName := strings.ToLower(col)
|
colName := strings.ToLower(col)
|
||||||
if alias, ok := o.fieldAlias[colName]; ok {
|
if alias, ok := o.fieldAlias[colName]; ok {
|
||||||
if fn, ok := o.fieldMapping[alias]; ok {
|
colName = strings.ToLower(alias)
|
||||||
out = append(out, sanitizeString(fn(obj)))
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
}
|
if fn, ok := o.fieldMapping[strings.ReplaceAll(colName, "_", "")]; ok {
|
||||||
if fn, ok := o.fieldMapping[colName]; ok {
|
|
||||||
out = append(out, sanitizeString(fn(obj)))
|
out = append(out, sanitizeString(fn(obj)))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -32,17 +32,17 @@ func TestTableOutput(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
t.Run("AddFieldAlias", func(t *testing.T) {
|
t.Run("AddFieldAlias", func(t *testing.T) {
|
||||||
to.AddFieldAlias("woodpecker_ci", "woodpecker ci")
|
to.AddFieldAlias("WoodpeckerCI", "wp")
|
||||||
if alias, ok := to.fieldAlias["woodpecker_ci"]; !ok || alias != "woodpecker ci" {
|
if alias, ok := to.fieldAlias["wp"]; !ok || alias != "WoodpeckerCI" {
|
||||||
t.Errorf("woodpecker_ci alias should be 'woodpecker ci', is: %v", alias)
|
t.Errorf("'wp' alias should resolve to 'WoodpeckerCI', is: %v", alias)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
t.Run("AddFieldOutputFn", func(t *testing.T) {
|
t.Run("AddFieldOutputFn", func(t *testing.T) {
|
||||||
to.AddFieldFn("woodpecker ci", FieldFn(func(_ any) string {
|
to.AddFieldFn("WoodpeckerCI", FieldFn(func(_ any) string {
|
||||||
return "WOODPECKER CI!!!"
|
return "WOODPECKER CI!!!"
|
||||||
}))
|
}))
|
||||||
if _, ok := to.fieldMapping["woodpecker ci"]; !ok {
|
if _, ok := to.fieldMapping["woodpeckerci"]; !ok {
|
||||||
t.Errorf("'woodpecker ci' field output fn should be set")
|
t.Errorf("'WoodpeckerCI' field output fn should be set")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
t.Run("ValidateColumns", func(t *testing.T) {
|
t.Run("ValidateColumns", func(t *testing.T) {
|
||||||
@ -54,14 +54,14 @@ func TestTableOutput(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
t.Run("WriteHeader", func(t *testing.T) {
|
t.Run("WriteHeader", func(t *testing.T) {
|
||||||
to.WriteHeader([]string{"woodpecker_ci", "name"})
|
to.WriteHeader([]string{"wp", "name"})
|
||||||
if wfs.String() != "WOODPECKER CI\tNAME\n" {
|
if wfs.String() != "WP\tNAME\n" {
|
||||||
t.Errorf("written header should be 'WOODPECKER CI\\tNAME\\n', is: %q", wfs.String())
|
t.Errorf("written header should be 'WOODPECKER CI\\tNAME\\n', is: %q", wfs.String())
|
||||||
}
|
}
|
||||||
wfs.Reset()
|
wfs.Reset()
|
||||||
})
|
})
|
||||||
t.Run("WriteLine", func(t *testing.T) {
|
t.Run("WriteLine", func(t *testing.T) {
|
||||||
_ = to.Write([]string{"woodpecker_ci", "name", "number"}, &testFieldsStruct{"test123", 1000000000})
|
_ = to.Write([]string{"wp", "name", "number"}, &testFieldsStruct{"test123", 1000000000})
|
||||||
if wfs.String() != "WOODPECKER CI!!!\ttest123\t1000000000\n" {
|
if wfs.String() != "WOODPECKER CI!!!\ttest123\t1000000000\n" {
|
||||||
t.Errorf("written line should be 'WOODPECKER CI!!!\\ttest123\\t1000000000\\n', is: %q", wfs.String())
|
t.Errorf("written line should be 'WOODPECKER CI!!!\\ttest123\\t1000000000\\n', is: %q", wfs.String())
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ var Command = &cli.Command{
|
|||||||
ArgsUsage: "<repo-id|repo-full-name> <pipeline> <environment>",
|
ArgsUsage: "<repo-id|repo-full-name> <pipeline> <environment>",
|
||||||
Action: deploy,
|
Action: deploy,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
common.FormatFlag(tmplDeployInfo),
|
common.FormatFlag(tmplDeployInfo, false),
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "branch",
|
Name: "branch",
|
||||||
Usage: "branch filter",
|
Usage: "branch filter",
|
||||||
|
@ -55,13 +55,9 @@ func pipelineOutput(c *cli.Command, pipelines []*woodpecker.Pipeline, fd ...io.W
|
|||||||
noHeader := c.Bool("output-no-headers")
|
noHeader := c.Bool("output-no-headers")
|
||||||
|
|
||||||
var out io.Writer
|
var out io.Writer
|
||||||
switch len(fd) {
|
|
||||||
case 0:
|
|
||||||
out = os.Stdout
|
out = os.Stdout
|
||||||
case 1:
|
if len(fd) > 0 {
|
||||||
out = fd[0]
|
out = fd[0]
|
||||||
default:
|
|
||||||
out = os.Stdout
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch outFmt {
|
switch outFmt {
|
||||||
|
@ -33,7 +33,7 @@ var pipelinePsCmd = &cli.Command{
|
|||||||
Usage: "show pipeline steps",
|
Usage: "show pipeline steps",
|
||||||
ArgsUsage: "<repo-id|repo-full-name> <pipeline>",
|
ArgsUsage: "<repo-id|repo-full-name> <pipeline>",
|
||||||
Action: pipelinePs,
|
Action: pipelinePs,
|
||||||
Flags: []cli.Flag{common.FormatFlag(tmplPipelinePs)},
|
Flags: []cli.Flag{common.FormatFlag(tmplPipelinePs, false)},
|
||||||
}
|
}
|
||||||
|
|
||||||
func pipelinePs(ctx context.Context, c *cli.Command) error {
|
func pipelinePs(ctx context.Context, c *cli.Command) error {
|
||||||
|
@ -31,7 +31,7 @@ var pipelineQueueCmd = &cli.Command{
|
|||||||
Usage: "show pipeline queue",
|
Usage: "show pipeline queue",
|
||||||
ArgsUsage: " ",
|
ArgsUsage: " ",
|
||||||
Action: pipelineQueue,
|
Action: pipelineQueue,
|
||||||
Flags: []cli.Flag{common.FormatFlag(tmplPipelineQueue)},
|
Flags: []cli.Flag{common.FormatFlag(tmplPipelineQueue, false)},
|
||||||
}
|
}
|
||||||
|
|
||||||
func pipelineQueue(ctx context.Context, c *cli.Command) error {
|
func pipelineQueue(ctx context.Context, c *cli.Command) error {
|
||||||
|
@ -15,11 +15,19 @@
|
|||||||
package repo
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
|
|
||||||
|
"go.woodpecker-ci.org/woodpecker/v3/cli/output"
|
||||||
"go.woodpecker-ci.org/woodpecker/v3/cli/repo/cron"
|
"go.woodpecker-ci.org/woodpecker/v3/cli/repo/cron"
|
||||||
"go.woodpecker-ci.org/woodpecker/v3/cli/repo/registry"
|
"go.woodpecker-ci.org/woodpecker/v3/cli/repo/registry"
|
||||||
"go.woodpecker-ci.org/woodpecker/v3/cli/repo/secret"
|
"go.woodpecker-ci.org/woodpecker/v3/cli/repo/secret"
|
||||||
|
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Command exports the repository command.
|
// Command exports the repository command.
|
||||||
@ -40,3 +48,84 @@ var Command = &cli.Command{
|
|||||||
repoUpdateCmd,
|
repoUpdateCmd,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func repoOutput(c *cli.Command, repos []*woodpecker.Repo, fd ...io.Writer) error {
|
||||||
|
outFmt, outOpt := output.ParseOutputOptions(c.String("output"))
|
||||||
|
noHeader := c.Bool("output-no-headers")
|
||||||
|
|
||||||
|
legacyFmt := c.String("format")
|
||||||
|
if legacyFmt != "" {
|
||||||
|
log.Warn().Msgf("the --format flag is deprecated, please use --output instead")
|
||||||
|
|
||||||
|
outFmt = "go-template"
|
||||||
|
outOpt = []string{legacyFmt}
|
||||||
|
}
|
||||||
|
|
||||||
|
var out io.Writer
|
||||||
|
out = os.Stdout
|
||||||
|
if len(fd) > 0 {
|
||||||
|
out = fd[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
switch outFmt {
|
||||||
|
case "go-template":
|
||||||
|
if len(outOpt) < 1 {
|
||||||
|
return fmt.Errorf("%w: missing template", output.ErrOutputOptionRequired)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl, err := template.New("_").Parse(outOpt[0] + "\n")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := tmpl.Execute(out, repos); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "table":
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
table := output.NewTable(out)
|
||||||
|
|
||||||
|
// Add custom field mapping for nested Trusted fields
|
||||||
|
table.AddFieldFn("TrustedNetwork", func(obj any) string {
|
||||||
|
repo, ok := obj.(*woodpecker.Repo)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return output.YesNo(repo.Trusted.Network)
|
||||||
|
})
|
||||||
|
table.AddFieldFn("TrustedSecurity", func(obj any) string {
|
||||||
|
repo, ok := obj.(*woodpecker.Repo)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return output.YesNo(repo.Trusted.Security)
|
||||||
|
})
|
||||||
|
table.AddFieldFn("TrustedVolume", func(obj any) string {
|
||||||
|
repo, ok := obj.(*woodpecker.Repo)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return output.YesNo(repo.Trusted.Volumes)
|
||||||
|
})
|
||||||
|
|
||||||
|
table.AddFieldAlias("Is_Active", "Active")
|
||||||
|
table.AddFieldAlias("Is_SCM_Private", "SCM_Private")
|
||||||
|
|
||||||
|
cols := []string{"Full_Name", "Branch", "Forge_URL", "Visibility", "SCM_Private", "Active", "Allow_Pull"}
|
||||||
|
|
||||||
|
if len(outOpt) > 0 {
|
||||||
|
cols = outOpt
|
||||||
|
}
|
||||||
|
if !noHeader {
|
||||||
|
table.WriteHeader(cols)
|
||||||
|
}
|
||||||
|
for _, resource := range repos {
|
||||||
|
if err := table.Write(cols, resource); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
table.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -16,8 +16,6 @@ package repo
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"os"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
|
|
||||||
@ -30,9 +28,9 @@ var repoListCmd = &cli.Command{
|
|||||||
Name: "ls",
|
Name: "ls",
|
||||||
Usage: "list all repos",
|
Usage: "list all repos",
|
||||||
ArgsUsage: " ",
|
ArgsUsage: " ",
|
||||||
Action: repoList,
|
Action: List,
|
||||||
Flags: []cli.Flag{
|
Flags: append(common.OutputFlags("table"), []cli.Flag{
|
||||||
common.FormatFlag(tmplRepoList),
|
common.FormatFlag("", true),
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "org",
|
Name: "org",
|
||||||
Usage: "filter by organization",
|
Usage: "filter by organization",
|
||||||
@ -41,40 +39,38 @@ var repoListCmd = &cli.Command{
|
|||||||
Name: "all",
|
Name: "all",
|
||||||
Usage: "query all repos, including inactive ones",
|
Usage: "query all repos, including inactive ones",
|
||||||
},
|
},
|
||||||
},
|
}...),
|
||||||
}
|
}
|
||||||
|
|
||||||
func repoList(ctx context.Context, c *cli.Command) error {
|
func List(ctx context.Context, c *cli.Command) error {
|
||||||
client, err := internal.NewClient(ctx, c)
|
client, err := internal.NewClient(ctx, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
repos, err := repoList(c, client)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return repoOutput(c, repos)
|
||||||
|
}
|
||||||
|
|
||||||
|
func repoList(c *cli.Command, client woodpecker.Client) ([]*woodpecker.Repo, error) {
|
||||||
|
repos := make([]*woodpecker.Repo, 0)
|
||||||
opt := woodpecker.RepoListOptions{
|
opt := woodpecker.RepoListOptions{
|
||||||
All: c.Bool("all"),
|
All: c.Bool("all"),
|
||||||
}
|
}
|
||||||
|
|
||||||
repos, err := client.RepoList(opt)
|
raw, err := client.RepoList(opt)
|
||||||
if err != nil || len(repos) == 0 {
|
if err != nil || len(raw) == 0 {
|
||||||
return err
|
return nil, err
|
||||||
}
|
|
||||||
|
|
||||||
tmpl, err := template.New("_").Parse(c.String("format") + "\n")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
org := c.String("org")
|
org := c.String("org")
|
||||||
for _, repo := range repos {
|
for _, repo := range raw {
|
||||||
if org != "" && org != repo.Owner {
|
if org != "" && org != repo.Owner {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := tmpl.Execute(os.Stdout, repo); err != nil {
|
repos = append(repos, repo)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
return repos, nil
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Template for repository list items.
|
|
||||||
var tmplRepoList = "\x1b[33m{{ .FullName }}\x1b[0m (id: {{ .ID }}, forgeRemoteID: {{ .ForgeRemoteID }}, isActive: {{ .IsActive }})"
|
|
||||||
|
@ -16,56 +16,45 @@ package repo
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"os"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
|
|
||||||
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
|
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
|
||||||
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
|
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
|
||||||
|
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
|
||||||
)
|
)
|
||||||
|
|
||||||
var repoShowCmd = &cli.Command{
|
var repoShowCmd = &cli.Command{
|
||||||
Name: "show",
|
Name: "show",
|
||||||
Usage: "show repository information",
|
Usage: "show repository information",
|
||||||
ArgsUsage: "<repo-id|repo-full-name>",
|
ArgsUsage: "<repo-id|repo-full-name>",
|
||||||
Action: repoShow,
|
Action: Show,
|
||||||
Flags: []cli.Flag{common.FormatFlag(tmplRepoInfo)},
|
Flags: common.OutputFlags("table"),
|
||||||
}
|
}
|
||||||
|
|
||||||
func repoShow(ctx context.Context, c *cli.Command) error {
|
func Show(ctx context.Context, c *cli.Command) error {
|
||||||
repoIDOrFullName := c.Args().First()
|
|
||||||
client, err := internal.NewClient(ctx, c)
|
client, err := internal.NewClient(ctx, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
|
repo, err := repoShow(c, client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
return repoOutput(c, []*woodpecker.Repo{repo})
|
||||||
|
}
|
||||||
|
|
||||||
|
func repoShow(c *cli.Command, client woodpecker.Client) (*woodpecker.Repo, error) {
|
||||||
|
repoIDOrFullName := c.Args().First()
|
||||||
|
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
repo, err := client.Repo(repoID)
|
repo, err := client.Repo(repoID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpl, err := template.New("_").Parse(c.String("format"))
|
return repo, nil
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return tmpl.Execute(os.Stdout, repo)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// tTemplate for repo information.
|
|
||||||
var tmplRepoInfo = `Owner: {{ .Owner }}
|
|
||||||
Repo: {{ .Name }}
|
|
||||||
URL: {{ .ForgeURL }}
|
|
||||||
Config path: {{ .Config }}
|
|
||||||
Visibility: {{ .Visibility }}
|
|
||||||
Private: {{ .IsSCMPrivate }}
|
|
||||||
Trusted: {{ .IsTrusted }}
|
|
||||||
Gated: {{ .IsGated }}
|
|
||||||
Require approval for: {{ .RequireApproval }}
|
|
||||||
Clone url: {{ .Clone }}
|
|
||||||
Allow pull-requests: {{ .AllowPullRequests }}
|
|
||||||
`
|
|
||||||
|
72
cli/repo/repo_show_test.go
Normal file
72
cli/repo/repo_show_test.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/urfave/cli/v3"
|
||||||
|
|
||||||
|
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
|
||||||
|
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker/mocks"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRepoShow(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
repoID int64
|
||||||
|
mockRepo *woodpecker.Repo
|
||||||
|
mockError error
|
||||||
|
expectedError bool
|
||||||
|
expected *woodpecker.Repo
|
||||||
|
args []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid repo by ID",
|
||||||
|
repoID: 123,
|
||||||
|
mockRepo: &woodpecker.Repo{Name: "test-repo"},
|
||||||
|
expected: &woodpecker.Repo{Name: "test-repo"},
|
||||||
|
args: []string{"show", "123"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid repo by full name",
|
||||||
|
repoID: 456,
|
||||||
|
mockRepo: &woodpecker.Repo{ID: 456, Name: "repo", Owner: "owner"},
|
||||||
|
expected: &woodpecker.Repo{ID: 456, Name: "repo", Owner: "owner"},
|
||||||
|
args: []string{"show", "owner/repo"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid repo ID",
|
||||||
|
repoID: 999,
|
||||||
|
expectedError: true,
|
||||||
|
args: []string{"show", "invalid"},
|
||||||
|
mockError: errors.New("repo not found"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
mockClient := mocks.NewClient(t)
|
||||||
|
mockClient.On("Repo", tt.repoID).Return(tt.mockRepo, tt.mockError).Maybe()
|
||||||
|
mockClient.On("RepoLookup", "owner/repo").Return(tt.mockRepo, nil).Maybe()
|
||||||
|
|
||||||
|
command := repoShowCmd
|
||||||
|
command.Writer = io.Discard
|
||||||
|
command.Action = func(_ context.Context, c *cli.Command) error {
|
||||||
|
output, err := repoShow(c, mockClient)
|
||||||
|
if tt.expectedError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.expected, output)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = command.Run(context.Background(), tt.args)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -31,7 +31,7 @@ var repoSyncCmd = &cli.Command{
|
|||||||
Usage: "synchronize the repository list",
|
Usage: "synchronize the repository list",
|
||||||
ArgsUsage: " ",
|
ArgsUsage: " ",
|
||||||
Action: repoSync,
|
Action: repoSync,
|
||||||
Flags: []cli.Flag{common.FormatFlag(tmplRepoList)},
|
Flags: []cli.Flag{common.FormatFlag(tmplRepoList, false)},
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove this and add an option to the list cmd as we do not store the remote repo list anymore
|
// TODO: remove this and add an option to the list cmd as we do not store the remote repo list anymore
|
||||||
@ -66,3 +66,6 @@ func repoSync(ctx context.Context, c *cli.Command) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Template for repository list items.
|
||||||
|
var tmplRepoList = "\x1b[33m{{ .FullName }}\x1b[0m (id: {{ .ID }}, forgeRemoteID: {{ .ForgeRemoteID }}, isActive: {{ .IsActive }})"
|
||||||
|
85
cli/repo/repo_test.go
Normal file
85
cli/repo/repo_test.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/urfave/cli/v3"
|
||||||
|
|
||||||
|
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
|
||||||
|
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRepoOutput(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
expected string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "table output with default columns",
|
||||||
|
args: []string{},
|
||||||
|
expected: "FULL NAME BRANCH FORGE URL VISIBILITY SCM PRIVATE ACTIVE ALLOW PULL\norg/repo1 main git.example.com public no yes yes\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "table output with custom columns",
|
||||||
|
args: []string{"output", "--output", "table=Name,Forge_URL,Trusted_Network"},
|
||||||
|
expected: "NAME FORGE URL TRUSTED NETWORK\nrepo1 git.example.com yes\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "table output with no header",
|
||||||
|
args: []string{"output", "--output-no-headers"},
|
||||||
|
expected: "org/repo1 main git.example.com public no yes yes\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "go-template output",
|
||||||
|
args: []string{"output", "--output", "go-template={{range . }}{{.Name}} {{.ForgeURL}} {{.Trusted.Network}}{{end}}"},
|
||||||
|
expected: "repo1 git.example.com true\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
repos := []*woodpecker.Repo{
|
||||||
|
{
|
||||||
|
Name: "repo1",
|
||||||
|
FullName: "org/repo1",
|
||||||
|
ForgeURL: "git.example.com",
|
||||||
|
Branch: "main",
|
||||||
|
Visibility: "public",
|
||||||
|
IsActive: true,
|
||||||
|
AllowPull: true,
|
||||||
|
Trusted: woodpecker.TrustedConfiguration{
|
||||||
|
Network: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
command := &cli.Command{
|
||||||
|
Writer: io.Discard,
|
||||||
|
Name: "output",
|
||||||
|
Flags: common.OutputFlags("table"),
|
||||||
|
Action: func(_ context.Context, c *cli.Command) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := repoOutput(c, repos, &buf)
|
||||||
|
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.expected, buf.String())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = command.Run(context.Background(), tt.args)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user