Merge branch 'main' into fix/code-highlight-colors

This commit is contained in:
pat-s 2024-12-01 20:23:25 +01:00
commit 3662bfc94e
No known key found for this signature in database
GPG key ID: 3C6318841EF78925
260 changed files with 5689 additions and 3113 deletions

View file

@ -10,7 +10,7 @@ repos:
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/golangci/golangci-lint
rev: v1.62.0
rev: v1.62.2
hooks:
- id: golangci-lint
- repo: https://github.com/igorshubovych/markdownlint-cli

View file

@ -99,7 +99,7 @@ steps:
release:
depends_on:
- checksums
image: woodpeckerci/plugin-release:0.2.1
image: woodpeckerci/plugin-release:0.2.2
settings:
api_key:
from_secret: github_token

View file

@ -59,7 +59,7 @@ steps:
- event: manual
deploy-preview:
image: docker.io/woodpeckerci/plugin-surge-preview:1.3.2
image: docker.io/woodpeckerci/plugin-surge-preview:1.3.3
settings:
path: 'docs/build/'
surge_token:

View file

@ -17,14 +17,20 @@ package admin
import (
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/admin/loglevel"
"go.woodpecker-ci.org/woodpecker/v2/cli/admin/registry"
"go.woodpecker-ci.org/woodpecker/v2/cli/admin/secret"
"go.woodpecker-ci.org/woodpecker/v2/cli/admin/user"
)
// Command exports the admin command set.
var Command = &cli.Command{
Name: "admin",
Usage: "administer server settings",
Usage: "manage server settings",
Commands: []*cli.Command{
loglevel.Command,
registry.Command,
secret.Command,
user.Command,
},
}

View file

@ -29,7 +29,7 @@ import (
var Command = &cli.Command{
Name: "log-level",
ArgsUsage: "[level]",
Usage: "get the logging level of the server, or set it with [level]",
Usage: "retrieve log level from server, or set it with [level]",
Action: logLevel,
}
@ -59,6 +59,6 @@ func logLevel(ctx context.Context, c *cli.Command) error {
}
}
log.Info().Msgf("logging level: %s", ll.Level)
log.Info().Msgf("log level: %s", ll.Level)
return nil
}

View file

@ -25,8 +25,8 @@ var Command = &cli.Command{
Commands: []*cli.Command{
registryCreateCmd,
registryDeleteCmd,
registryUpdateCmd,
registryInfoCmd,
registryListCmd,
registryShowCmd,
registryUpdateCmd,
},
}

View file

@ -27,7 +27,7 @@ import (
var registryCreateCmd = &cli.Command{
Name: "add",
Usage: "adds a registry",
Usage: "add a registry",
Action: registryCreate,
Flags: []cli.Flag{
&cli.StringFlag{

View file

@ -23,6 +23,7 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
)
var registryListCmd = &cli.Command{
@ -42,7 +43,9 @@ func registryList(ctx context.Context, c *cli.Command) error {
return err
}
list, err := client.GlobalRegistryList()
opt := woodpecker.RegistryListOptions{}
list, err := client.GlobalRegistryList(opt)
if err != nil {
return err
}

View file

@ -25,10 +25,10 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
)
var registryInfoCmd = &cli.Command{
Name: "info",
Usage: "display registry info",
Action: registryInfo,
var registryShowCmd = &cli.Command{
Name: "show",
Usage: "show registry information",
Action: registryShow,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "hostname",
@ -39,7 +39,7 @@ var registryInfoCmd = &cli.Command{
},
}
func registryInfo(ctx context.Context, c *cli.Command) error {
func registryShow(ctx context.Context, c *cli.Command) error {
var (
hostname = c.String("hostname")
format = c.String("format") + "\n"

View file

@ -0,0 +1,32 @@
// Copyright 2023 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package secret
import (
"github.com/urfave/cli/v3"
)
// Command exports the secret command.
var Command = &cli.Command{
Name: "secret",
Usage: "manage global secrets",
Commands: []*cli.Command{
secretCreateCmd,
secretDeleteCmd,
secretListCmd,
secretShowCmd,
secretUpdateCmd,
},
}

View file

@ -0,0 +1,82 @@
// Copyright 2023 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package secret
import (
"context"
"os"
"strings"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
)
var secretCreateCmd = &cli.Command{
Name: "add",
Usage: "add a secret",
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretCreate,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "value",
Usage: "secret value",
},
&cli.StringSliceFlag{
Name: "event",
Usage: "secret limited to these events",
},
&cli.StringSliceFlag{
Name: "image",
Usage: "secret limited to these images",
},
},
}
func secretCreate(ctx context.Context, c *cli.Command) error {
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
secret := &woodpecker.Secret{
Name: strings.ToLower(c.String("name")),
Value: c.String("value"),
Images: c.StringSlice("image"),
Events: c.StringSlice("event"),
}
if len(secret.Events) == 0 {
secret.Events = defaultSecretEvents
}
if strings.HasPrefix(secret.Value, "@") {
path := strings.TrimPrefix(secret.Value, "@")
out, err := os.ReadFile(path)
if err != nil {
return err
}
secret.Value = string(out)
}
_, err = client.GlobalSecretCreate(secret)
return err
}
var defaultSecretEvents = []string{
woodpecker.EventPush,
woodpecker.EventTag,
woodpecker.EventRelease,
woodpecker.EventDeploy,
}

View file

@ -33,12 +33,6 @@ var secretListCmd = &cli.Command{
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretList,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "global",
Usage: "global secret",
},
common.OrgFlag,
common.RepoFlag,
common.FormatFlag(tmplSecretList, true),
},
}
@ -51,30 +45,13 @@ func secretList(ctx context.Context, c *cli.Command) error {
return err
}
global, orgID, repoID, err := parseTargetArgs(client, c)
opt := woodpecker.SecretListOptions{}
list, err := client.GlobalSecretList(opt)
if err != nil {
return err
}
var list []*woodpecker.Secret
switch {
case global:
list, err = client.GlobalSecretList()
if err != nil {
return err
}
case orgID != -1:
list, err = client.OrgSecretList(orgID)
if err != nil {
return err
}
default:
list, err = client.SecretList(repoID)
if err != nil {
return err
}
}
tmpl, err := template.New("_").Funcs(secretFuncMap).Parse(format)
if err != nil {
return err

View file

@ -0,0 +1,47 @@
// Copyright 2023 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package secret
import (
"context"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
)
var secretDeleteCmd = &cli.Command{
Name: "rm",
Usage: "remove a secret",
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretDelete,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "name",
Usage: "secret name",
},
},
}
func secretDelete(ctx context.Context, c *cli.Command) error {
secretName := c.String("name")
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
return client.GlobalSecretDelete(secretName)
}

View file

@ -0,0 +1,76 @@
// Copyright 2023 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package secret
import (
"context"
"os"
"strings"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
)
var secretUpdateCmd = &cli.Command{
Name: "update",
Usage: "update a secret",
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretUpdate,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "name",
Usage: "secret name",
},
&cli.StringFlag{
Name: "value",
Usage: "secret value",
},
&cli.StringSliceFlag{
Name: "event",
Usage: "secret limited to these events",
},
&cli.StringSliceFlag{
Name: "image",
Usage: "secret limited to these images",
},
},
}
func secretUpdate(ctx context.Context, c *cli.Command) error {
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
secret := &woodpecker.Secret{
Name: strings.ToLower(c.String("name")),
Value: c.String("value"),
Images: c.StringSlice("image"),
Events: c.StringSlice("event"),
}
if strings.HasPrefix(secret.Value, "@") {
path := strings.TrimPrefix(secret.Value, "@")
out, err := os.ReadFile(path)
if err != nil {
return err
}
secret.Value = string(out)
}
_, err = client.GlobalSecretUpdate(secret)
return err
}

View file

@ -0,0 +1,68 @@
// Copyright 2023 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package secret
import (
"context"
"fmt"
"html/template"
"os"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
)
var secretShowCmd = &cli.Command{
Name: "show",
Usage: "show secret information",
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretShow,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "name",
Usage: "secret name",
},
common.FormatFlag(tmplSecretList, true),
},
}
func secretShow(ctx context.Context, c *cli.Command) error {
var (
secretName = c.String("name")
format = c.String("format") + "\n"
)
if secretName == "" {
return fmt.Errorf("secret name is missing")
}
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
secret, err := client.GlobalSecret(secretName)
if err != nil {
return err
}
tmpl, err := template.New("_").Funcs(secretFuncMap).Parse(format)
if err != nil {
return err
}
return tmpl.Execute(os.Stdout, secret)
}

View file

@ -23,9 +23,9 @@ var Command = &cli.Command{
Name: "user",
Usage: "manage users",
Commands: []*cli.Command{
userListCmd,
userInfoCmd,
userAddCmd,
userListCmd,
userRemoveCmd,
userShowCmd,
},
}

View file

@ -26,7 +26,7 @@ import (
var userAddCmd = &cli.Command{
Name: "add",
Usage: "adds a user",
Usage: "add a user",
ArgsUsage: "<username>",
Action: userAdd,
}

View file

@ -23,6 +23,7 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
)
var userListCmd = &cli.Command{
@ -39,7 +40,9 @@ func userList(ctx context.Context, c *cli.Command) error {
return err
}
users, err := client.UserList()
opt := woodpecker.UserListOptions{}
users, err := client.UserList(opt)
if err != nil || len(users) == 0 {
return err
}

View file

@ -26,15 +26,15 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
)
var userInfoCmd = &cli.Command{
Name: "info",
Usage: "show user details",
var userShowCmd = &cli.Command{
Name: "show",
Usage: "show user information",
ArgsUsage: "<username>",
Action: userInfo,
Action: userShow,
Flags: []cli.Flag{common.FormatFlag(tmplUserInfo)},
}
func userInfo(ctx context.Context, c *cli.Command) error {
func userShow(ctx context.Context, c *cli.Command) error {
client, err := internal.NewClient(ctx, c)
if err != nil {
return err

View file

@ -35,18 +35,18 @@ func Before(ctx context.Context, c *cli.Command) (context.Context, error) {
waitForUpdateCheck, cancelWaitForUpdate = context.WithCancelCause(context.Background())
defer cancelWaitForUpdate(errors.New("update check finished"))
log.Debug().Msg("Checking for updates ...")
log.Debug().Msg("checking for updates ...")
newVersion, err := update.CheckForUpdate(waitForUpdateCheck, false) //nolint:contextcheck
if err != nil {
log.Error().Err(err).Msgf("Failed to check for updates")
log.Error().Err(err).Msgf("failed to check for updates")
return
}
if newVersion != nil {
log.Warn().Msgf("A new version of woodpecker-cli is available: %s. Update by running: %s update", newVersion.Version, c.Root().Name)
log.Warn().Msgf("new version of woodpecker-cli is available: %s, update with: %s update", newVersion.Version, c.Root().Name)
} else {
log.Debug().Msgf("No update required")
log.Debug().Msgf("no update required")
}
}(ctx)
@ -59,7 +59,7 @@ func After(_ context.Context, _ *cli.Command) error {
case <-waitForUpdateCheck.Done():
// When the actual command already finished, we still wait 500ms for the update check to finish
case <-time.After(time.Millisecond * 500):
log.Debug().Msg("Update check stopped due to timeout")
log.Debug().Msg("update check stopped due to timeout")
cancelWaitForUpdate(errors.New("update check timeout"))
}
}

View file

@ -44,7 +44,7 @@ func Load(ctx context.Context, c *cli.Command) error {
}
if config.ServerURL == "" || config.Token == "" {
log.Info().Msg("The woodpecker-cli is not yet set up. Please run `woodpecker-cli setup` or provide the required environment variables / flags.")
log.Info().Msg("woodpecker-cli is not set up, run `woodpecker-cli setup` or provide required environment variables/flags")
return errors.New("woodpecker-cli is not configured")
}
@ -63,7 +63,7 @@ func Load(ctx context.Context, c *cli.Command) error {
return err
}
log.Debug().Any("config", config).Msg("Loaded config")
log.Debug().Any("config", config).Msg("loaded config")
return nil
}
@ -93,16 +93,16 @@ func Get(_ context.Context, c *cli.Command, _configPath string) (*Config, error)
return nil, err
}
log.Debug().Str("configPath", configPath).Msg("Checking for config file")
log.Debug().Str("configPath", configPath).Msg("checking for config file")
content, err := os.ReadFile(configPath)
switch {
case err != nil && !os.IsNotExist(err):
log.Debug().Err(err).Msg("Failed to read the config file")
log.Debug().Err(err).Msg("failed to read the config file")
return nil, err
case err != nil && os.IsNotExist(err):
log.Debug().Msg("The config file does not exist")
log.Debug().Msg("config file does not exist")
default:
configFromFile := &Config{}
@ -111,7 +111,7 @@ func Get(_ context.Context, c *cli.Command, _configPath string) (*Config, error)
return nil, err
}
conf.MergeIfNotSet(configFromFile)
log.Debug().Msg("Loaded config from file")
log.Debug().Msg("loaded config from file")
}
// if server or token are explicitly set, use them
@ -123,11 +123,11 @@ func Get(_ context.Context, c *cli.Command, _configPath string) (*Config, error)
service := c.Root().Name
secret, err := keyring.Get(service, conf.ServerURL)
if errors.Is(err, keyring.ErrUnsupportedPlatform) {
log.Warn().Msg("Keyring is not supported on this platform")
log.Warn().Msg("keyring is not supported on this platform")
return conf, nil
}
if errors.Is(err, keyring.ErrNotFound) {
log.Warn().Msg("Token not found in keyring")
log.Warn().Msg("token not found in keyring")
return conf, nil
}
conf.Token = secret

View file

@ -40,12 +40,12 @@ var Command = &cli.Command{
&cli.StringSliceFlag{
Sources: cli.EnvVars("WOODPECKER_PLUGINS_PRIVILEGED"),
Name: "plugins-privileged",
Usage: "Allow plugins to run in privileged mode, if environment variable is defined but empty there will be none",
Usage: "allow plugins to run in privileged mode, if set empty, there is no",
},
&cli.StringSliceFlag{
Sources: cli.EnvVars("WOODPECKER_PLUGINS_TRUSTED_CLONE"),
Name: "plugins-trusted-clone",
Usage: "Plugins which are trusted to handle Git credentials in clone steps",
Usage: "plugins that are trusted to handle Git credentials in cloning steps",
Value: constant.TrustedClonePlugins,
},
&cli.BoolFlag{

View file

@ -18,6 +18,7 @@ import (
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/org/registry"
"go.woodpecker-ci.org/woodpecker/v2/cli/org/secret"
)
// Command exports the org command set.
@ -26,5 +27,6 @@ var Command = &cli.Command{
Usage: "manage organizations",
Commands: []*cli.Command{
registry.Command,
secret.Command,
},
}

View file

@ -29,9 +29,9 @@ var Command = &cli.Command{
Commands: []*cli.Command{
registryCreateCmd,
registryDeleteCmd,
registryUpdateCmd,
registryInfoCmd,
registryListCmd,
registryShowCmd,
registryUpdateCmd,
},
}

View file

@ -28,7 +28,7 @@ import (
var registryCreateCmd = &cli.Command{
Name: "add",
Usage: "adds a registry",
Usage: "add a registry",
ArgsUsage: "[org-id|org-full-name]",
Action: registryCreate,
Flags: []cli.Flag{

View file

@ -23,6 +23,7 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
)
var registryListCmd = &cli.Command{
@ -49,7 +50,9 @@ func registryList(ctx context.Context, c *cli.Command) error {
return err
}
list, err := client.OrgRegistryList(orgID)
opt := woodpecker.RegistryListOptions{}
list, err := client.OrgRegistryList(orgID, opt)
if err != nil {
return err
}

View file

@ -25,11 +25,11 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
)
var registryInfoCmd = &cli.Command{
Name: "info",
Usage: "display registry info",
var registryShowCmd = &cli.Command{
Name: "show",
Usage: "show registry information",
ArgsUsage: "[org-id|org-full-name]",
Action: registryInfo,
Action: registryShow,
Flags: []cli.Flag{
common.OrgFlag,
&cli.StringFlag{
@ -41,7 +41,7 @@ var registryInfoCmd = &cli.Command{
},
}
func registryInfo(ctx context.Context, c *cli.Command) error {
func registryShow(ctx context.Context, c *cli.Command) error {
var (
hostname = c.String("hostname")
format = c.String("format") + "\n"

60
cli/org/secret/secret.go Normal file
View file

@ -0,0 +1,60 @@
// Copyright 2023 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package secret
import (
"strconv"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
)
// Command exports the secret command.
var Command = &cli.Command{
Name: "secret",
Usage: "manage secrets",
Commands: []*cli.Command{
secretCreateCmd,
secretDeleteCmd,
secretListCmd,
secretShowCmd,
secretUpdateCmd,
},
}
func parseTargetArgs(client woodpecker.Client, c *cli.Command) (orgID int64, err error) {
orgIDOrName := c.String("organization")
if orgIDOrName == "" {
orgIDOrName = c.Args().First()
}
if orgIDOrName == "" {
if err := cli.ShowSubcommandHelp(c); err != nil {
return -1, err
}
}
if orgID, err := strconv.ParseInt(orgIDOrName, 10, 64); err == nil {
return orgID, nil
}
org, err := client.OrgLookup(orgIDOrName)
if err != nil {
return -1, err
}
return org.ID, nil
}

View file

@ -28,16 +28,11 @@ import (
var secretCreateCmd = &cli.Command{
Name: "add",
Usage: "adds a secret",
Usage: "add a secret",
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretCreate,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "global",
Usage: "global secret",
},
common.OrgFlag,
common.RepoFlag,
&cli.StringFlag{
Name: "name",
Usage: "secret name",
@ -81,22 +76,12 @@ func secretCreate(ctx context.Context, c *cli.Command) error {
secret.Value = string(out)
}
global, orgID, repoID, err := parseTargetArgs(client, c)
orgID, err := parseTargetArgs(client, c)
if err != nil {
return err
}
if global {
_, err = client.GlobalSecretCreate(secret)
return err
}
if orgID != -1 {
_, err = client.OrgSecretCreate(orgID, secret)
return err
}
_, err = client.SecretCreate(repoID, secret)
_, err = client.OrgSecretCreate(orgID, secret)
return err
}

View file

@ -0,0 +1,87 @@
// Copyright 2023 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package secret
import (
"context"
"html/template"
"os"
"strings"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
)
var secretListCmd = &cli.Command{
Name: "ls",
Usage: "list secrets",
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretList,
Flags: []cli.Flag{
common.OrgFlag,
common.FormatFlag(tmplSecretList, true),
},
}
func secretList(ctx context.Context, c *cli.Command) error {
format := c.String("format") + "\n"
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
orgID, err := parseTargetArgs(client, c)
if err != nil {
return err
}
opt := woodpecker.SecretListOptions{}
list, err := client.OrgSecretList(orgID, opt)
if err != nil {
return err
}
tmpl, err := template.New("_").Funcs(secretFuncMap).Parse(format)
if err != nil {
return err
}
for _, secret := range list {
if err := tmpl.Execute(os.Stdout, secret); err != nil {
return err
}
}
return nil
}
// Template for secret list items.
var tmplSecretList = "\x1b[33m{{ .Name }} \x1b[0m" + `
Events: {{ list .Events }}
{{- if .Images }}
Images: {{ list .Images }}
{{- else }}
Images: <any>
{{- end }}
`
var secretFuncMap = template.FuncMap{
"list": func(s []string) string {
return strings.Join(s, ", ")
},
}

View file

@ -0,0 +1,54 @@
// Copyright 2023 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package secret
import (
"context"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
)
var secretDeleteCmd = &cli.Command{
Name: "rm",
Usage: "remove a secret",
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretDelete,
Flags: []cli.Flag{
common.OrgFlag,
&cli.StringFlag{
Name: "name",
Usage: "secret name",
},
},
}
func secretDelete(ctx context.Context, c *cli.Command) error {
secretName := c.String("name")
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
orgID, err := parseTargetArgs(client, c)
if err != nil {
return err
}
return client.OrgSecretDelete(orgID, secretName)
}

View file

@ -0,0 +1,83 @@
// Copyright 2023 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package secret
import (
"context"
"os"
"strings"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
)
var secretUpdateCmd = &cli.Command{
Name: "update",
Usage: "update a secret",
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretUpdate,
Flags: []cli.Flag{
common.OrgFlag,
&cli.StringFlag{
Name: "name",
Usage: "secret name",
},
&cli.StringFlag{
Name: "value",
Usage: "secret value",
},
&cli.StringSliceFlag{
Name: "event",
Usage: "limit secret to these event",
},
&cli.StringSliceFlag{
Name: "image",
Usage: "limit secret to these image",
},
},
}
func secretUpdate(ctx context.Context, c *cli.Command) error {
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
secret := &woodpecker.Secret{
Name: strings.ToLower(c.String("name")),
Value: c.String("value"),
Images: c.StringSlice("image"),
Events: c.StringSlice("event"),
}
if strings.HasPrefix(secret.Value, "@") {
path := strings.TrimPrefix(secret.Value, "@")
out, err := os.ReadFile(path)
if err != nil {
return err
}
secret.Value = string(out)
}
orgID, err := parseTargetArgs(client, c)
if err != nil {
return err
}
_, err = client.OrgSecretUpdate(orgID, secret)
return err
}

View file

@ -0,0 +1,74 @@
// Copyright 2023 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package secret
import (
"context"
"fmt"
"html/template"
"os"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
)
var secretShowCmd = &cli.Command{
Name: "show",
Usage: "show secret information",
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretShow,
Flags: []cli.Flag{
common.OrgFlag,
&cli.StringFlag{
Name: "name",
Usage: "secret name",
},
common.FormatFlag(tmplSecretList, true),
},
}
func secretShow(ctx context.Context, c *cli.Command) error {
var (
secretName = c.String("name")
format = c.String("format") + "\n"
)
if secretName == "" {
return fmt.Errorf("secret name is missing")
}
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
orgID, err := parseTargetArgs(client, c)
if err != nil {
return err
}
secret, err := client.OrgSecret(orgID, secretName)
if err != nil {
return err
}
tmpl, err := template.New("_").Funcs(secretFuncMap).Parse(format)
if err != nil {
return err
}
return tmpl.Execute(os.Stdout, secret)
}

View file

@ -147,12 +147,12 @@ func (o *Table) Write(columns []string, obj any) error {
colName := strings.ToLower(col)
if alias, ok := o.fieldAlias[colName]; ok {
if fn, ok := o.fieldMapping[alias]; ok {
out = append(out, fn(obj))
out = append(out, sanitizeString(fn(obj)))
continue
}
}
if fn, ok := o.fieldMapping[colName]; ok {
out = append(out, fn(obj))
out = append(out, sanitizeString(fn(obj)))
continue
}
if value, ok := dataL[strings.ReplaceAll(colName, "_", "")]; ok {
@ -165,10 +165,10 @@ func (o *Table) Write(columns []string, obj any) error {
continue
}
if s, ok := value.(string); ok {
out = append(out, NA(s))
out = append(out, NA(sanitizeString(s)))
continue
}
out = append(out, fmt.Sprintf("%v", value))
out = append(out, sanitizeString(value))
}
}
_, _ = fmt.Fprintln(o.w, strings.Join(out, "\t"))
@ -201,3 +201,9 @@ func fieldName(name string) string {
}
return string(out)
}
func sanitizeString(value any) string {
str := fmt.Sprintf("%v", value)
replacer := strings.NewReplacer("\n", " ", "\r", " ")
return strings.TrimSpace(replacer.Replace(str))
}

View file

@ -53,7 +53,7 @@ var Command = &cli.Command{
&cli.StringSliceFlag{
Name: "param",
Aliases: []string{"p"},
Usage: "custom parameters to be injected into the step environment. Format: KEY=value",
Usage: "custom parameters to inject into the step environment. Format: KEY=value",
},
},
}
@ -80,14 +80,14 @@ func deploy(ctx context.Context, c *cli.Command) error {
return err
}
branch = repo.DefaultBranch
branch = repo.Branch
}
pipelineArg := c.Args().Get(1)
var number int64
if pipelineArg == "last" {
// Fetch the pipeline number from the last pipeline
pipelines, err := client.PipelineList(repoID)
pipelines, err := client.PipelineList(repoID, woodpecker.PipelineListOptions{})
if err != nil {
return err
}
@ -121,9 +121,12 @@ func deploy(ctx context.Context, c *cli.Command) error {
return fmt.Errorf("please specify the target environment (i.e. production)")
}
params := internal.ParseKeyPair(c.StringSlice("param"))
opt := woodpecker.DeployOptions{
DeployTo: env,
Params: internal.ParseKeyPair(c.StringSlice("param")),
}
deploy, err := client.Deploy(repoID, number, env, params)
deploy, err := client.Deploy(repoID, number, opt)
if err != nil {
return err
}

View file

@ -48,7 +48,7 @@ func pipelineKill(ctx context.Context, c *cli.Command) (err error) {
return err
}
err = client.PipelineKill(repoID, number)
err = client.PipelineDelete(repoID, number)
if err != nil {
return err
}

View file

@ -26,7 +26,7 @@ import (
var pipelineLastCmd = &cli.Command{
Name: "last",
Usage: "show latest pipeline details",
Usage: "show latest pipeline information",
ArgsUsage: "<repo-id|repo-full-name>",
Action: pipelineLast,
Flags: append(common.OutputFlags("table"), []cli.Flag{
@ -49,7 +49,11 @@ func pipelineLast(ctx context.Context, c *cli.Command) error {
return err
}
pipeline, err := client.PipelineLast(repoID, c.String("branch"))
opt := woodpecker.PipelineLastOptions{
Branch: c.String("branch"),
}
pipeline, err := client.PipelineLast(repoID, opt)
if err != nil {
return err
}

View file

@ -16,6 +16,7 @@ package pipeline
import (
"context"
"time"
"github.com/urfave/cli/v3"
@ -24,6 +25,7 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
)
//nolint:mnd
func buildPipelineListCmd() *cli.Command {
return &cli.Command{
Name: "ls",
@ -46,9 +48,26 @@ func buildPipelineListCmd() *cli.Command {
&cli.IntFlag{
Name: "limit",
Usage: "limit the list size",
//nolint:mnd
Value: 25,
},
&cli.TimestampFlag{
Name: "before",
Usage: "only return pipelines before this date (RFC3339)",
Config: cli.TimestampConfig{
Layouts: []string{
time.RFC3339,
},
},
},
&cli.TimestampFlag{
Name: "after",
Usage: "only return pipelines after this date (RFC3339)",
Config: cli.TimestampConfig{
Layouts: []string{
time.RFC3339,
},
},
},
}...),
}
}
@ -58,14 +77,14 @@ func List(ctx context.Context, c *cli.Command) error {
if err != nil {
return err
}
resources, err := pipelineList(ctx, c, client)
resources, err := pipelineList(c, client)
if err != nil {
return err
}
return pipelineOutput(c, resources)
}
func pipelineList(_ context.Context, c *cli.Command, client woodpecker.Client) ([]woodpecker.Pipeline, error) {
func pipelineList(c *cli.Command, client woodpecker.Client) ([]woodpecker.Pipeline, error) {
resources := make([]woodpecker.Pipeline, 0)
repoIDOrFullName := c.Args().First()
@ -74,7 +93,18 @@ func pipelineList(_ context.Context, c *cli.Command, client woodpecker.Client) (
return resources, err
}
pipelines, err := client.PipelineList(repoID)
opt := woodpecker.PipelineListOptions{}
before := c.Timestamp("before")
after := c.Timestamp("after")
if !before.IsZero() {
opt.Before = before
}
if !after.IsZero() {
opt.After = after
}
pipelines, err := client.PipelineList(repoID, opt)
if err != nil {
return resources, err
}

View file

@ -107,13 +107,13 @@ func TestPipelineList(t *testing.T) {
for _, tt := range testtases {
t.Run(tt.name, func(t *testing.T) {
mockClient := mocks.NewClient(t)
mockClient.On("PipelineList", mock.Anything).Return(tt.pipelines, tt.pipelineErr)
mockClient.On("PipelineList", mock.Anything, mock.Anything).Return(tt.pipelines, tt.pipelineErr)
mockClient.On("RepoLookup", mock.Anything).Return(&woodpecker.Repo{ID: tt.repoID}, nil)
command := buildPipelineListCmd()
command.Writer = io.Discard
command.Action = func(ctx context.Context, c *cli.Command) error {
pipelines, err := pipelineList(ctx, c, mockClient)
command.Action = func(_ context.Context, c *cli.Command) error {
pipelines, err := pipelineList(c, mockClient)
if tt.wantErr != nil {
assert.EqualError(t, err, tt.wantErr.Error())
return nil

View file

@ -24,5 +24,6 @@ var Command = &cli.Command{
Usage: "manage logs",
Commands: []*cli.Command{
logPurgeCmd,
logShowCmd,
},
}

View file

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package pipeline
package log
import (
"context"
@ -27,14 +27,14 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
)
var pipelineLogsCmd = &cli.Command{
Name: "logs",
var logShowCmd = &cli.Command{
Name: "show",
Usage: "show pipeline logs",
ArgsUsage: "<repo-id|repo-full-name> <pipeline> [step-number|step-name]",
Action: pipelineLogs,
Action: logShow,
}
func pipelineLogs(ctx context.Context, c *cli.Command) error {
func logShow(ctx context.Context, c *cli.Command) error {
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(ctx, c)
if err != nil {
@ -59,17 +59,17 @@ func pipelineLogs(ctx context.Context, c *cli.Command) error {
stepArg := c.Args().Get(2) //nolint:mnd
if len(stepArg) == 0 {
return showPipelineLog(client, repoID, number)
return pipelineLog(client, repoID, number)
}
step, err := internal.ParseStep(client, repoID, number, stepArg)
if err != nil {
return fmt.Errorf("invalid step '%s': %w", stepArg, err)
}
return showStepLog(client, repoID, number, step)
return stepLog(client, repoID, number, step)
}
func showPipelineLog(client woodpecker.Client, repoID, number int64) error {
func pipelineLog(client woodpecker.Client, repoID, number int64) error {
pipeline, err := client.Pipeline(repoID, number)
if err != nil {
return err
@ -85,7 +85,7 @@ func showPipelineLog(client woodpecker.Client, repoID, number int64) error {
if err := tmpl.Execute(os.Stdout, map[string]any{"workflow": workflow, "step": step}); err != nil {
return err
}
err := showStepLog(client, repoID, number, step.ID)
err := stepLog(client, repoID, number, step.ID)
if err != nil {
return err
}
@ -95,7 +95,7 @@ func showPipelineLog(client woodpecker.Client, repoID, number int64) error {
return nil
}
func showStepLog(client woodpecker.Client, repoID, number, step int64) error {
func stepLog(client woodpecker.Client, repoID, number, step int64) error {
logs, err := client.StepLogEntries(repoID, number, step)
if err != nil {
return err

View file

@ -23,6 +23,8 @@ import (
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/output"
"go.woodpecker-ci.org/woodpecker/v2/cli/pipeline/deploy"
"go.woodpecker-ci.org/woodpecker/v2/cli/pipeline/log"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
)
@ -31,18 +33,20 @@ var Command = &cli.Command{
Name: "pipeline",
Usage: "manage pipelines",
Commands: []*cli.Command{
buildPipelineListCmd(),
pipelineLastCmd,
pipelineLogsCmd,
pipelineInfoCmd,
pipelineStopCmd,
pipelineStartCmd,
pipelineApproveCmd,
pipelineDeclineCmd,
pipelineQueueCmd,
pipelineKillCmd,
pipelinePsCmd,
pipelineCreateCmd,
pipelineDeclineCmd,
deploy.Command,
pipelineKillCmd,
pipelineLastCmd,
buildPipelineListCmd(),
log.Command,
pipelinePsCmd,
pipelinePurgeCmd,
pipelineQueueCmd,
pipelineShowCmd,
pipelineStartCmd,
pipelineStopCmd,
},
}

View file

@ -23,7 +23,7 @@ func TestPipelineOutput(t *testing.T) {
{
name: "table output with default columns",
args: []string{},
expected: "NUMBER STATUS EVENT BRANCH MESSAGE AUTHOR\n1 success push main message John Doe\n",
expected: "NUMBER STATUS EVENT BRANCH MESSAGE AUTHOR\n1 success push main message multiline John Doe\n",
},
{
name: "table output with custom columns",
@ -33,7 +33,7 @@ func TestPipelineOutput(t *testing.T) {
{
name: "table output with no header",
args: []string{"output", "--output-no-headers"},
expected: "1 success push main message John Doe\n",
expected: "1 success push main message multiline John Doe\n",
},
{
name: "go-template output",
@ -53,8 +53,8 @@ func TestPipelineOutput(t *testing.T) {
Status: "success",
Event: "push",
Branch: "main",
Message: "message",
Author: "John Doe",
Message: "message\nmultiline",
Author: "John Doe\n",
},
}

View file

@ -25,6 +25,7 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
)
var pipelinePsCmd = &cli.Command{
@ -51,7 +52,7 @@ func pipelinePs(ctx context.Context, c *cli.Command) error {
if pipelineArg == "last" || len(pipelineArg) == 0 {
// Fetch the pipeline number from the last pipeline
pipeline, err := client.PipelineLast(repoID, "")
pipeline, err := client.PipelineLast(repoID, woodpecker.PipelineLastOptions{})
if err != nil {
return err
}

157
cli/pipeline/purge.go Normal file
View file

@ -0,0 +1,157 @@
// Copyright 2022 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pipeline
import (
"context"
"fmt"
"time"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
shared_utils "go.woodpecker-ci.org/woodpecker/v2/shared/utils"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
)
//nolint:mnd
var pipelinePurgeCmd = &cli.Command{
Name: "purge",
Usage: "purge pipelines",
ArgsUsage: "<repo-id|repo-full-name>",
Action: Purge,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "older-than",
Usage: "remove pipelines older than the specified time limit",
Required: true,
},
&cli.IntFlag{
Name: "keep-min",
Usage: "minimum number of pipelines to keep",
Value: 10,
},
&cli.BoolFlag{
Name: "dry-run",
Usage: "disable non-read api calls",
Value: false,
},
},
}
func Purge(ctx context.Context, c *cli.Command) error {
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
return pipelinePurge(c, client)
}
func pipelinePurge(c *cli.Command, client woodpecker.Client) (err error) {
repoIDOrFullName := c.Args().First()
if len(repoIDOrFullName) == 0 {
return fmt.Errorf("missing required argument repo-id / repo-full-name")
}
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return fmt.Errorf("invalid repo '%s': %w", repoIDOrFullName, err)
}
olderThan := c.String("older-than")
keepMin := c.Int("keep-min")
dryRun := c.Bool("dry-run")
duration, err := time.ParseDuration(olderThan)
if err != nil {
return err
}
var pipelinesKeep []*woodpecker.Pipeline
if keepMin > 0 {
pipelinesKeep, err = fetchPipelinesToKeep(client, repoID, int(keepMin))
if err != nil {
return err
}
}
pipelines, err := fetchPipelines(client, repoID, duration)
if err != nil {
return err
}
// Create a map of pipeline IDs to keep
keepMap := make(map[int64]struct{})
for _, p := range pipelinesKeep {
keepMap[p.ID] = struct{}{}
}
// Filter pipelines to only include those not in keepMap
var pipelinesToPurge []*woodpecker.Pipeline
for _, p := range pipelines {
if _, exists := keepMap[p.ID]; !exists {
pipelinesToPurge = append(pipelinesToPurge, p)
}
}
msgPrefix := ""
if dryRun {
msgPrefix = "DRY-RUN: "
}
for i, p := range pipelinesToPurge {
// cspell:words spurge
log.Debug().Msgf("%spurge %v/%v pipelines from repo '%v'", msgPrefix, i+1, len(pipelinesToPurge), repoIDOrFullName)
if dryRun {
continue
}
err := client.PipelineDelete(repoID, p.ID)
if err != nil {
return err
}
}
return nil
}
func fetchPipelinesToKeep(client woodpecker.Client, repoID int64, keepMin int) ([]*woodpecker.Pipeline, error) {
if keepMin <= 0 {
return nil, nil
}
return shared_utils.Paginate(func(page int) ([]*woodpecker.Pipeline, error) {
return client.PipelineList(repoID,
woodpecker.PipelineListOptions{
ListOptions: woodpecker.ListOptions{
Page: page,
},
},
)
}, keepMin)
}
func fetchPipelines(client woodpecker.Client, repoID int64, duration time.Duration) ([]*woodpecker.Pipeline, error) {
return shared_utils.Paginate(func(page int) ([]*woodpecker.Pipeline, error) {
return client.PipelineList(repoID,
woodpecker.PipelineListOptions{
ListOptions: woodpecker.ListOptions{
Page: page,
},
After: time.Now().Add(-duration),
},
)
}, -1)
}

105
cli/pipeline/purge_test.go Normal file
View file

@ -0,0 +1,105 @@
package pipeline
import (
"context"
"errors"
"io"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker/mocks"
)
func TestPipelinePurge(t *testing.T) {
tests := []struct {
name string
repoID int64
args []string
pipelinesKeep []*woodpecker.Pipeline
pipelines []*woodpecker.Pipeline
wantDelete int
wantErr error
}{
{
name: "success with no pipelines to purge",
repoID: 1,
args: []string{"purge", "--older-than", "1h", "repo/name"},
pipelinesKeep: []*woodpecker.Pipeline{
{ID: 1},
},
pipelines: []*woodpecker.Pipeline{},
},
{
name: "success with pipelines to purge",
repoID: 1,
args: []string{"purge", "--older-than", "1h", "repo/name"},
pipelinesKeep: []*woodpecker.Pipeline{
{ID: 1},
},
pipelines: []*woodpecker.Pipeline{
{ID: 1},
{ID: 2},
{ID: 3},
},
wantDelete: 2,
},
{
name: "error on invalid duration",
repoID: 1,
args: []string{"purge", "--older-than", "invalid", "repo/name"},
wantErr: errors.New("time: invalid duration \"invalid\""),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockClient := mocks.NewClient(t)
mockClient.On("RepoLookup", mock.Anything).Maybe().Return(&woodpecker.Repo{ID: tt.repoID}, nil)
mockClient.On("PipelineList", mock.Anything, mock.Anything).Return(func(_ int64, opt woodpecker.PipelineListOptions) ([]*woodpecker.Pipeline, error) {
// Return keep pipelines for first call
if opt.After.IsZero() {
if opt.Page == 1 {
return tt.pipelinesKeep, nil
}
return []*woodpecker.Pipeline{}, nil
}
// Return pipelines to purge for calls with After filter
if !opt.After.IsZero() {
if opt.Page == 1 {
return tt.pipelines, nil
}
return []*woodpecker.Pipeline{}, nil
}
return []*woodpecker.Pipeline{}, nil
}).Maybe()
if tt.wantDelete > 0 {
mockClient.On("PipelineDelete", tt.repoID, mock.Anything).Return(nil).Times(tt.wantDelete)
}
command := pipelinePurgeCmd
command.Writer = io.Discard
command.Action = func(_ context.Context, c *cli.Command) error {
err := pipelinePurge(c, mockClient)
if tt.wantErr != nil {
assert.EqualError(t, err, tt.wantErr.Error())
return nil
}
assert.NoError(t, err)
return nil
}
_ = command.Run(context.Background(), tt.args)
})
}
}

View file

@ -25,15 +25,15 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
)
var pipelineInfoCmd = &cli.Command{
Name: "info",
Usage: "show pipeline details",
var pipelineShowCmd = &cli.Command{
Name: "show",
Usage: "show pipeline information",
ArgsUsage: "<repo-id|repo-full-name> [pipeline]",
Action: pipelineInfo,
Action: pipelineShow,
Flags: common.OutputFlags("table"),
}
func pipelineInfo(ctx context.Context, c *cli.Command) error {
func pipelineShow(ctx context.Context, c *cli.Command) error {
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(ctx, c)
if err != nil {
@ -48,7 +48,7 @@ func pipelineInfo(ctx context.Context, c *cli.Command) error {
var number int64
if pipelineArg == "last" || len(pipelineArg) == 0 {
// Fetch the pipeline number from the last pipeline
pipeline, err := client.PipelineLast(repoID, "")
pipeline, err := client.PipelineLast(repoID, woodpecker.PipelineLastOptions{})
if err != nil {
return err
}

View file

@ -23,6 +23,7 @@ import (
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
)
var pipelineStartCmd = &cli.Command{
@ -34,7 +35,7 @@ var pipelineStartCmd = &cli.Command{
&cli.StringSliceFlag{
Name: "param",
Aliases: []string{"p"},
Usage: "custom parameters to be injected into the step environment. Format: KEY=value",
Usage: "custom parameters to inject into the step environment. Format: KEY=value",
},
},
}
@ -54,7 +55,7 @@ func pipelineStart(ctx context.Context, c *cli.Command) (err error) {
var number int64
if pipelineArg == "last" {
// Fetch the pipeline number from the last pipeline
pipeline, err := client.PipelineLast(repoID, "")
pipeline, err := client.PipelineLast(repoID, woodpecker.PipelineLastOptions{})
if err != nil {
return err
}
@ -69,9 +70,11 @@ func pipelineStart(ctx context.Context, c *cli.Command) (err error) {
}
}
params := internal.ParseKeyPair(c.StringSlice("param"))
opt := woodpecker.PipelineStartOptions{
Params: internal.ParseKeyPair(c.StringSlice("param")),
}
pipeline, err := client.PipelineStart(repoID, number, params)
pipeline, err := client.PipelineStart(repoID, number, opt)
if err != nil {
return err
}

View file

@ -25,8 +25,8 @@ var Command = &cli.Command{
Commands: []*cli.Command{
cronCreateCmd,
cronDeleteCmd,
cronUpdateCmd,
cronInfoCmd,
cronListCmd,
cronShowCmd,
cronUpdateCmd,
},
}

View file

@ -23,6 +23,7 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
)
var cronListCmd = &cli.Command{
@ -52,7 +53,8 @@ func cronList(ctx context.Context, c *cli.Command) error {
if err != nil {
return err
}
list, err := client.CronList(repoID)
opt := woodpecker.CronListOptions{}
list, err := client.CronList(repoID, opt)
if err != nil {
return err
}

View file

@ -25,11 +25,11 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
)
var cronInfoCmd = &cli.Command{
Name: "info",
Usage: "display info about a cron job",
var cronShowCmd = &cli.Command{
Name: "show",
Usage: "show cron job information",
ArgsUsage: "[repo-id|repo-full-name]",
Action: cronInfo,
Action: cronShow,
Flags: []cli.Flag{
common.RepoFlag,
&cli.StringFlag{
@ -41,7 +41,7 @@ var cronInfoCmd = &cli.Command{
},
}
func cronInfo(ctx context.Context, c *cli.Command) error {
func cronShow(ctx context.Context, c *cli.Command) error {
var (
cronID = c.Int("id")
repoIDOrFullName = c.String("repository")

View file

@ -28,9 +28,9 @@ var Command = &cli.Command{
Commands: []*cli.Command{
registryCreateCmd,
registryDeleteCmd,
registryUpdateCmd,
registryInfoCmd,
registryListCmd,
registryShowCmd,
registryUpdateCmd,
},
}

View file

@ -28,7 +28,7 @@ import (
var registryCreateCmd = &cli.Command{
Name: "add",
Usage: "adds a registry",
Usage: "add a registry",
ArgsUsage: "[repo-id|repo-full-name]",
Action: registryCreate,
Flags: []cli.Flag{

View file

@ -23,6 +23,7 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
)
var registryListCmd = &cli.Command{
@ -49,7 +50,9 @@ func registryList(ctx context.Context, c *cli.Command) error {
return err
}
list, err := client.RegistryList(repoID)
opt := woodpecker.RegistryListOptions{}
list, err := client.RegistryList(repoID, opt)
if err != nil {
return err
}

View file

@ -25,11 +25,11 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
)
var registryInfoCmd = &cli.Command{
Name: "info",
Usage: "display registry info",
var registryShowCmd = &cli.Command{
Name: "show",
Usage: "show registry information",
ArgsUsage: "[repo-id|repo-full-name]",
Action: registryInfo,
Action: registryShow,
Flags: []cli.Flag{
common.RepoFlag,
&cli.StringFlag{
@ -41,7 +41,7 @@ var registryInfoCmd = &cli.Command{
},
}
func registryInfo(ctx context.Context, c *cli.Command) error {
func registryShow(ctx context.Context, c *cli.Command) error {
var (
hostname = c.String("hostname")
format = c.String("format") + "\n"

View file

@ -17,7 +17,9 @@ package repo
import (
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/repo/cron"
"go.woodpecker-ci.org/woodpecker/v2/cli/repo/registry"
"go.woodpecker-ci.org/woodpecker/v2/cli/repo/secret"
)
// Command exports the repository command.
@ -25,14 +27,16 @@ var Command = &cli.Command{
Name: "repo",
Usage: "manage repositories",
Commands: []*cli.Command{
repoListCmd,
repoInfoCmd,
repoAddCmd,
repoUpdateCmd,
repoChownCmd,
cron.Command,
repoListCmd,
registry.Command,
repoRemoveCmd,
repoRepairCmd,
repoChownCmd,
secret.Command,
repoShowCmd,
repoSyncCmd,
registry.Command,
repoUpdateCmd,
},
}

View file

@ -22,6 +22,7 @@ import (
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
)
var repoAddCmd = &cli.Command{
@ -43,7 +44,11 @@ func repoAdd(ctx context.Context, c *cli.Command) error {
return err
}
repo, err := client.RepoPost(int64(forgeRemoteID))
opt := woodpecker.RepoPostOptions{
ForgeRemoteID: int64(forgeRemoteID),
}
repo, err := client.RepoPost(opt)
if err != nil {
return err
}

View file

@ -23,6 +23,7 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
)
var repoListCmd = &cli.Command{
@ -36,6 +37,10 @@ var repoListCmd = &cli.Command{
Name: "org",
Usage: "filter by organization",
},
&cli.BoolFlag{
Name: "all",
Usage: "query all repos, including inactive ones",
},
},
}
@ -45,7 +50,11 @@ func repoList(ctx context.Context, c *cli.Command) error {
return err
}
repos, err := client.RepoList()
opt := woodpecker.RepoListOptions{
All: c.Bool("all"),
}
repos, err := client.RepoList(opt)
if err != nil || len(repos) == 0 {
return err
}
@ -68,4 +77,4 @@ func repoList(ctx context.Context, c *cli.Command) error {
}
// Template for repository list items.
var tmplRepoList = "\x1b[33m{{ .FullName }}\x1b[0m (id: {{ .ID }}, forgeRemoteID: {{ .ForgeRemoteID }})"
var tmplRepoList = "\x1b[33m{{ .FullName }}\x1b[0m (id: {{ .ID }}, forgeRemoteID: {{ .ForgeRemoteID }}, isActive: {{ .IsActive }})"

View file

@ -25,15 +25,15 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
)
var repoInfoCmd = &cli.Command{
Name: "info",
Usage: "show repository details",
var repoShowCmd = &cli.Command{
Name: "show",
Usage: "show repository information",
ArgsUsage: "<repo-id|repo-full-name>",
Action: repoInfo,
Action: repoShow,
Flags: []cli.Flag{common.FormatFlag(tmplRepoInfo)},
}
func repoInfo(ctx context.Context, c *cli.Command) error {
func repoShow(ctx context.Context, c *cli.Command) error {
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(ctx, c)
if err != nil {

View file

@ -23,6 +23,7 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
)
var repoSyncCmd = &cli.Command{
@ -40,7 +41,11 @@ func repoSync(ctx context.Context, c *cli.Command) error {
return err
}
repos, err := client.RepoListOpts(true)
opt := woodpecker.RepoListOptions{
All: true,
}
repos, err := client.RepoList(opt)
if err != nil || len(repos) == 0 {
return err
}

View file

@ -53,7 +53,7 @@ var repoUpdateCmd = &cli.Command{
},
&cli.StringFlag{
Name: "config",
Usage: "repository configuration path (e.g. .woodpecker.yml)",
Usage: "repository configuration path. Example: .woodpecker.yml",
},
&cli.IntFlag{
Name: "pipeline-counter",
@ -61,7 +61,7 @@ var repoUpdateCmd = &cli.Command{
},
&cli.BoolFlag{
Name: "unsafe",
Usage: "validate updating the pipeline-counter is unsafe",
Usage: "allow unsafe operations",
},
},
}

View file

@ -15,10 +15,6 @@
package secret
import (
"fmt"
"strconv"
"strings"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
@ -32,52 +28,17 @@ var Command = &cli.Command{
Commands: []*cli.Command{
secretCreateCmd,
secretDeleteCmd,
secretUpdateCmd,
secretInfoCmd,
secretListCmd,
secretShowCmd,
secretUpdateCmd,
},
}
func parseTargetArgs(client woodpecker.Client, c *cli.Command) (global bool, orgID, repoID int64, err error) {
if c.Bool("global") {
return true, -1, -1, nil
}
func parseTargetArgs(client woodpecker.Client, c *cli.Command) (repoID int64, err error) {
repoIDOrFullName := c.String("repository")
if repoIDOrFullName == "" {
repoIDOrFullName = c.Args().First()
}
orgIDOrName := c.String("organization")
if orgIDOrName == "" && repoIDOrFullName == "" {
if err := cli.ShowSubcommandHelp(c); err != nil {
return false, -1, -1, err
}
return false, -1, -1, fmt.Errorf("missing arguments")
}
if orgIDOrName != "" && repoIDOrFullName == "" {
if orgID, err := strconv.ParseInt(orgIDOrName, 10, 64); err == nil {
return false, orgID, -1, nil
}
org, err := client.OrgLookup(orgIDOrName)
if err != nil {
return false, -1, -1, err
}
return false, org.ID, -1, nil
}
if orgIDOrName != "" && !strings.Contains(repoIDOrFullName, "/") {
repoIDOrFullName = orgIDOrName + "/" + repoIDOrFullName
}
repoID, err = internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return false, -1, -1, err
}
return false, -1, repoID, nil
return internal.ParseRepo(client, repoIDOrFullName)
}

View file

@ -0,0 +1,93 @@
// Copyright 2023 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package secret
import (
"context"
"os"
"strings"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
)
var secretCreateCmd = &cli.Command{
Name: "add",
Usage: "add a secret",
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretCreate,
Flags: []cli.Flag{
common.RepoFlag,
&cli.StringFlag{
Name: "name",
Usage: "secret name",
},
&cli.StringFlag{
Name: "value",
Usage: "secret value",
},
&cli.StringSliceFlag{
Name: "event",
Usage: "limit secret to these events",
},
&cli.StringSliceFlag{
Name: "image",
Usage: "limit secret to these images",
},
},
}
func secretCreate(ctx context.Context, c *cli.Command) error {
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
secret := &woodpecker.Secret{
Name: strings.ToLower(c.String("name")),
Value: c.String("value"),
Images: c.StringSlice("image"),
Events: c.StringSlice("event"),
}
if len(secret.Events) == 0 {
secret.Events = defaultSecretEvents
}
if strings.HasPrefix(secret.Value, "@") {
path := strings.TrimPrefix(secret.Value, "@")
out, err := os.ReadFile(path)
if err != nil {
return err
}
secret.Value = string(out)
}
repoID, err := parseTargetArgs(client, c)
if err != nil {
return err
}
_, err = client.SecretCreate(repoID, secret)
return err
}
var defaultSecretEvents = []string{
woodpecker.EventPush,
woodpecker.EventTag,
woodpecker.EventRelease,
woodpecker.EventDeploy,
}

View file

@ -0,0 +1,87 @@
// Copyright 2023 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package secret
import (
"context"
"html/template"
"os"
"strings"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
)
var secretListCmd = &cli.Command{
Name: "ls",
Usage: "list secrets",
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretList,
Flags: []cli.Flag{
common.RepoFlag,
common.FormatFlag(tmplSecretList, true),
},
}
func secretList(ctx context.Context, c *cli.Command) error {
format := c.String("format") + "\n"
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
repoID, err := parseTargetArgs(client, c)
if err != nil {
return err
}
opt := woodpecker.SecretListOptions{}
list, err := client.SecretList(repoID, opt)
if err != nil {
return err
}
tmpl, err := template.New("_").Funcs(secretFuncMap).Parse(format)
if err != nil {
return err
}
for _, secret := range list {
if err := tmpl.Execute(os.Stdout, secret); err != nil {
return err
}
}
return nil
}
// Template for secret list items.
var tmplSecretList = "\x1b[33m{{ .Name }} \x1b[0m" + `
Events: {{ list .Events }}
{{- if .Images }}
Images: {{ list .Images }}
{{- else }}
Images: <any>
{{- end }}
`
var secretFuncMap = template.FuncMap{
"list": func(s []string) string {
return strings.Join(s, ", ")
},
}

View file

@ -29,11 +29,6 @@ var secretDeleteCmd = &cli.Command{
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretDelete,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "global",
Usage: "global secret",
},
common.OrgFlag,
common.RepoFlag,
&cli.StringFlag{
Name: "name",
@ -50,16 +45,10 @@ func secretDelete(ctx context.Context, c *cli.Command) error {
return err
}
global, orgID, repoID, err := parseTargetArgs(client, c)
repoID, err := parseTargetArgs(client, c)
if err != nil {
return err
}
if global {
return client.GlobalSecretDelete(secretName)
}
if orgID != -1 {
return client.OrgSecretDelete(orgID, secretName)
}
return client.SecretDelete(repoID, secretName)
}

View file

@ -32,11 +32,6 @@ var secretUpdateCmd = &cli.Command{
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretUpdate,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "global",
Usage: "global secret",
},
common.OrgFlag,
common.RepoFlag,
&cli.StringFlag{
Name: "name",
@ -48,11 +43,11 @@ var secretUpdateCmd = &cli.Command{
},
&cli.StringSliceFlag{
Name: "event",
Usage: "secret limited to these events",
Usage: "limit secret to these events",
},
&cli.StringSliceFlag{
Name: "image",
Usage: "secret limited to these images",
Usage: "limit secret to these images",
},
},
}
@ -78,19 +73,11 @@ func secretUpdate(ctx context.Context, c *cli.Command) error {
secret.Value = string(out)
}
global, orgID, repoID, err := parseTargetArgs(client, c)
repoID, err := parseTargetArgs(client, c)
if err != nil {
return err
}
if global {
_, err = client.GlobalSecretUpdate(secret)
return err
}
if orgID != -1 {
_, err = client.OrgSecretUpdate(orgID, secret)
return err
}
_, err = client.SecretUpdate(repoID, secret)
return err
}

View file

@ -24,20 +24,14 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
)
var secretInfoCmd = &cli.Command{
Name: "info",
Usage: "display secret info",
var secretShowCmd = &cli.Command{
Name: "show",
Usage: "show secret information",
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretInfo,
Action: secretShow,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "global",
Usage: "global secret",
},
common.OrgFlag,
common.RepoFlag,
&cli.StringFlag{
Name: "name",
@ -47,7 +41,7 @@ var secretInfoCmd = &cli.Command{
},
}
func secretInfo(ctx context.Context, c *cli.Command) error {
func secretShow(ctx context.Context, c *cli.Command) error {
var (
secretName = c.String("name")
format = c.String("format") + "\n"
@ -62,28 +56,14 @@ func secretInfo(ctx context.Context, c *cli.Command) error {
return err
}
global, orgID, repoID, err := parseTargetArgs(client, c)
repoID, err := parseTargetArgs(client, c)
if err != nil {
return err
}
var secret *woodpecker.Secret
switch {
case global:
secret, err = client.GlobalSecret(secretName)
if err != nil {
return err
}
case orgID != -1:
secret, err = client.OrgSecret(orgID, secretName)
if err != nil {
return err
}
default:
secret, err = client.Secret(repoID, secretName)
if err != nil {
return err
}
secret, err := client.Secret(repoID, secretName)
if err != nil {
return err
}
tmpl, err := template.New("_").Funcs(secretFuncMap).Parse(format)

View file

@ -20,11 +20,11 @@ var Command = &cli.Command{
Flags: []cli.Flag{
&cli.StringFlag{
Name: "server",
Usage: "The URL of the woodpecker server",
Usage: "URL of the woodpecker server",
},
&cli.StringFlag{
Name: "token",
Usage: "The token to authenticate with the woodpecker server",
Usage: "token to authenticate with the woodpecker server",
},
},
Action: setup,
@ -41,7 +41,7 @@ func setup(ctx context.Context, c *cli.Command) error {
}
if !setupAgain {
log.Info().Msg("Configuration skipped")
log.Info().Msg("configuration skipped")
return nil
}
}
@ -87,7 +87,7 @@ func setup(ctx context.Context, c *cli.Command) error {
return err
}
log.Info().Msg("The woodpecker-cli has been successfully setup")
log.Info().Msg("woodpecker-cli has been successfully setup")
return nil
}

View file

@ -24,12 +24,12 @@ func receiveTokenFromUI(c context.Context, serverURL string) (string, error) {
srv.Handler = setupRouter(tokenReceived)
go func() {
log.Debug().Msgf("Listening for token response on :%d", port)
log.Debug().Msgf("listening for token response on :%d", port)
_ = srv.ListenAndServe()
}()
defer func() {
log.Debug().Msg("Shutting down server")
log.Debug().Msg("shutting down server")
_ = srv.Shutdown(c)
}()
@ -90,7 +90,7 @@ func setupRouter(tokenReceived chan string) *gin.Engine {
err := c.BindJSON(&data)
if err != nil {
log.Debug().Err(err).Msg("Failed to bind JSON")
log.Debug().Err(err).Msg("failed to bind JSON")
c.JSON(http.StatusBadRequest, gin.H{
"error": "invalid request",
})
@ -110,7 +110,7 @@ func setupRouter(tokenReceived chan string) *gin.Engine {
func openBrowser(url string) error {
var err error
log.Debug().Msgf("Opening browser with URL: %s", url)
log.Debug().Msgf("opening browser with URL: %s", url)
switch runtime.GOOS {
case "linux":

View file

@ -24,7 +24,7 @@ var Command = &cli.Command{
}
func update(ctx context.Context, c *cli.Command) error {
log.Info().Msg("Checking for updates ...")
log.Info().Msg("checking for updates ...")
newVersion, err := CheckForUpdate(ctx, c.Bool("force"))
if err != nil {
@ -32,11 +32,11 @@ func update(ctx context.Context, c *cli.Command) error {
}
if newVersion == nil {
fmt.Println("You are using the latest version of woodpecker-cli")
fmt.Println("you are using the latest version of woodpecker-cli")
return nil
}
log.Info().Msgf("New version %s is available! Updating ...", newVersion.Version)
log.Info().Msgf("new version %s is available! Updating ...", newVersion.Version)
var tarFilePath string
tarFilePath, err = downloadNewVersion(ctx, newVersion.AssetURL)
@ -44,14 +44,14 @@ func update(ctx context.Context, c *cli.Command) error {
return err
}
log.Debug().Msgf("New version %s has been downloaded successfully! Installing ...", newVersion.Version)
log.Debug().Msgf("new version %s has been downloaded successfully! Installing ...", newVersion.Version)
binFile, err := extractNewVersion(tarFilePath)
if err != nil {
return err
}
log.Debug().Msgf("New version %s has been extracted to %s", newVersion.Version, binFile)
log.Debug().Msgf("new version %s has been extracted to %s", newVersion.Version, binFile)
executablePathOrSymlink, err := os.Executable()
if err != nil {

View file

@ -22,10 +22,10 @@ func CheckForUpdate(ctx context.Context, force bool) (*NewVersion, error) {
}
func checkForUpdate(ctx context.Context, versionURL string, force bool) (*NewVersion, error) {
log.Debug().Msgf("Current version: %s", version.String())
log.Debug().Msgf("current version: %s", version.String())
if (version.String() == "dev" || strings.HasPrefix(version.String(), "next-")) && !force {
log.Debug().Msgf("Skipping update check for development & next versions")
log.Debug().Msgf("skipping update check for development/next versions")
return nil, nil
}
@ -61,11 +61,11 @@ func checkForUpdate(ctx context.Context, versionURL string, force bool) (*NewVer
// using the latest release
if installedVersion == upstreamVersion && !force {
log.Debug().Msgf("No new version available")
log.Debug().Msgf("no new version available")
return nil, nil
}
log.Debug().Msgf("New version available: %s", upstreamVersion)
log.Debug().Msgf("new version available: %s", upstreamVersion)
assetURL := fmt.Sprintf(githubBinaryURL, upstreamVersion, runtime.GOOS, runtime.GOARCH)
return &NewVersion{
@ -75,7 +75,7 @@ func checkForUpdate(ctx context.Context, versionURL string, force bool) (*NewVer
}
func downloadNewVersion(ctx context.Context, downloadURL string) (string, error) {
log.Debug().Msgf("Downloading new version from %s ...", downloadURL)
log.Debug().Msgf("downloading new version from %s ...", downloadURL)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, downloadURL, nil)
if err != nil {
@ -102,13 +102,13 @@ func downloadNewVersion(ctx context.Context, downloadURL string) (string, error)
return "", err
}
log.Debug().Msgf("New version downloaded to %s", file.Name())
log.Debug().Msgf("new version downloaded to %s", file.Name())
return file.Name(), nil
}
func extractNewVersion(tarFilePath string) (string, error) {
log.Debug().Msgf("Extracting new version from %s ...", tarFilePath)
log.Debug().Msgf("extracting new version from %s ...", tarFilePath)
tarFile, err := os.Open(tarFilePath)
if err != nil {
@ -132,7 +132,7 @@ func extractNewVersion(tarFilePath string) (string, error) {
return "", err
}
log.Debug().Msgf("New version extracted to %s", tmpDir)
log.Debug().Msgf("new version extracted to %s", tmpDir)
return path.Join(tmpDir, "woodpecker-cli"), nil
}

View file

@ -36,6 +36,9 @@ var flags = []cli.Flag{
Sources: cli.NewValueSourceChain(
cli.File(os.Getenv("WOODPECKER_AGENT_SECRET_FILE")),
cli.EnvVar("WOODPECKER_AGENT_SECRET")),
Config: cli.StringConfig{
TrimSpace: true,
},
},
&cli.BoolFlag{
Sources: cli.EnvVars("WOODPECKER_GRPC_SECURE"),

View file

@ -19,20 +19,14 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/cli/admin"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/cron"
"go.woodpecker-ci.org/woodpecker/v2/cli/deploy"
"go.woodpecker-ci.org/woodpecker/v2/cli/exec"
"go.woodpecker-ci.org/woodpecker/v2/cli/info"
"go.woodpecker-ci.org/woodpecker/v2/cli/lint"
"go.woodpecker-ci.org/woodpecker/v2/cli/log"
"go.woodpecker-ci.org/woodpecker/v2/cli/loglevel"
"go.woodpecker-ci.org/woodpecker/v2/cli/org"
"go.woodpecker-ci.org/woodpecker/v2/cli/pipeline"
"go.woodpecker-ci.org/woodpecker/v2/cli/repo"
"go.woodpecker-ci.org/woodpecker/v2/cli/secret"
"go.woodpecker-ci.org/woodpecker/v2/cli/setup"
"go.woodpecker-ci.org/woodpecker/v2/cli/update"
"go.woodpecker-ci.org/woodpecker/v2/cli/user"
"go.woodpecker-ci.org/woodpecker/v2/version"
)
@ -49,18 +43,12 @@ func newApp() *cli.Command {
app.Suggest = true
app.Commands = []*cli.Command{
admin.Command,
org.Command,
repo.Command,
pipeline.Command,
log.Command,
deploy.Command,
exec.Command,
info.Command,
secret.Command,
user.Command,
lint.Command,
loglevel.Command,
cron.Command,
org.Command,
pipeline.Command,
repo.Command,
setup.Command,
update.Command,
}

View file

@ -116,6 +116,9 @@ var flags = append([]cli.Flag{
Name: "grpc-secret",
Usage: "grpc jwt secret",
Value: "secret",
Config: cli.StringConfig{
TrimSpace: true,
},
},
&cli.StringFlag{
Sources: cli.EnvVars("WOODPECKER_METRICS_SERVER_ADDR"),
@ -217,6 +220,9 @@ var flags = append([]cli.Flag{
cli.EnvVar("WOODPECKER_AGENT_SECRET")),
Name: "agent-secret",
Usage: "server-agent shared password",
Config: cli.StringConfig{
TrimSpace: true,
},
},
&cli.BoolFlag{
Sources: cli.EnvVars("WOODPECKER_DISABLE_USER_AGENT_REGISTRATION"),
@ -248,6 +254,9 @@ var flags = append([]cli.Flag{
Aliases: []string{"datasource"}, // TODO: remove in v4.0.0
Usage: "database driver configuration string",
Value: datasourceDefaultValue(),
Config: cli.StringConfig{
TrimSpace: true,
},
},
&cli.StringFlag{
Sources: cli.NewValueSourceChain(
@ -255,6 +264,9 @@ var flags = append([]cli.Flag{
cli.EnvVar("WOODPECKER_PROMETHEUS_AUTH_TOKEN")),
Name: "prometheus-auth-token",
Usage: "token to secure prometheus metrics endpoint",
Config: cli.StringConfig{
TrimSpace: true,
},
},
&cli.StringFlag{
Sources: cli.EnvVars("WOODPECKER_STATUS_CONTEXT", "WOODPECKER_GITHUB_CONTEXT", "WOODPECKER_GITEA_CONTEXT"),
@ -354,6 +366,9 @@ var flags = append([]cli.Flag{
cli.EnvVar("WOODPECKER_BITBUCKET_DC_CLIENT_ID")),
Name: "forge-oauth-client",
Usage: "oauth2 client id",
Config: cli.StringConfig{
TrimSpace: true,
},
},
&cli.StringFlag{
Sources: cli.NewValueSourceChain(
@ -375,6 +390,9 @@ var flags = append([]cli.Flag{
cli.EnvVar("WOODPECKER_BITBUCKET_DC_CLIENT_SECRET")),
Name: "forge-oauth-secret",
Usage: "oauth2 client secret",
Config: cli.StringConfig{
TrimSpace: true,
},
},
&cli.BoolFlag{
Name: "forge-skip-verify",
@ -466,6 +484,9 @@ var flags = append([]cli.Flag{
cli.EnvVar("WOODPECKER_BITBUCKET_DC_GIT_USERNAME")),
Name: "bitbucket-dc-git-username",
Usage: "Bitbucket DataCenter/Server service account username",
Config: cli.StringConfig{
TrimSpace: true,
},
},
&cli.StringFlag{
Sources: cli.NewValueSourceChain(
@ -473,6 +494,9 @@ var flags = append([]cli.Flag{
cli.EnvVar("WOODPECKER_BITBUCKET_DC_GIT_PASSWORD")),
Name: "bitbucket-dc-git-password",
Usage: "Bitbucket DataCenter/Server service account password",
Config: cli.StringConfig{
TrimSpace: true,
},
},
//
// development flags
@ -500,6 +524,9 @@ var flags = append([]cli.Flag{
cli.EnvVar("WOODPECKER_ENCRYPTION_KEY")),
Name: "encryption-raw-key",
Usage: "Raw encryption key",
Config: cli.StringConfig{
TrimSpace: true,
},
},
&cli.StringFlag{
Sources: cli.EnvVars("WOODPECKER_ENCRYPTION_TINK_KEYSET_FILE"),

View file

@ -2966,6 +2966,30 @@ const docTemplate = `{
"description": "only return pipelines after this RFC3339 date",
"name": "after",
"in": "query"
},
{
"type": "string",
"description": "filter pipelines by branch",
"name": "branch",
"in": "query"
},
{
"type": "string",
"description": "filter pipelines by webhook events (comma separated)",
"name": "event",
"in": "query"
},
{
"type": "string",
"description": "filter pipelines by strings contained in ref",
"name": "ref",
"in": "query"
},
{
"type": "string",
"description": "filter pipelines by status",
"name": "status",
"in": "query"
}
],
"responses": {
@ -5070,8 +5094,11 @@ const docTemplate = `{
"name": {
"type": "string"
},
"netrc_only_trusted": {
"type": "boolean"
"netrc_trusted": {
"type": "array",
"items": {
"type": "string"
}
},
"org_id": {
"type": "integer"
@ -5124,8 +5151,11 @@ const docTemplate = `{
"description": "TODO: deprecated in favor of RequireApproval =\u003e Remove in next major release",
"type": "boolean"
},
"netrc_only_trusted": {
"type": "boolean"
"netrc_trusted": {
"type": "array",
"items": {
"type": "string"
}
},
"require_approval": {
"type": "string"

View file

@ -763,6 +763,25 @@ Woodpecker supports to define multiple workflows for a repository. Those workflo
Workflows that should run even on failure should set the `runs_on` tag. See [here](./25-workflows.md#flow-control) for an example.
## Advanced network options for steps
:::warning
Only allowed if 'Trusted Network' option is enabled in repo settings by an admin.
:::
### `dns`
If the backend engine understands to change the DNS server and lookup domain,
this options will be used to alter the default DNS config to a custom one for a specific step.
```yaml
steps:
- name: build
image: plugin/abc
dns: 1.2.3.4
dns_search: 'internal.company'
```
## Privileged mode
Woodpecker gives the ability to configure privileged mode in the YAML. You can use this parameter to launch containers with escalated capabilities.

View file

@ -39,16 +39,13 @@ Only server admins can set this option. If you are not a server admin this optio
:::
## Only inject Git credentials into trusted clone plugins
## Custom trusted clone plugins
The clone step may require Git credentials (e.g. for private repos) which are injected via `netrc`.
By default, they are only injected into trusted clone plugins listed in the env var `WOODPECKER_PLUGINS_TRUSTED_CLONE`.
If this option is disabled, the Git credentials are injected into every clone plugin, regardless of whether it is trusted or not.
They are only injected into trusted plugins listed in the env var `WOODPECKER_PLUGINS_TRUSTED_CLONE` or in this repo setting.
:::note
This option has no effect on steps other than the clone step.
:::
This allows you to use a trusted plugin for in the clone section or as a step to pull or push using your git credentials.
## Project visibility

View file

@ -236,25 +236,25 @@ const config: Config = {
sidebarPath: require.resolve('./sidebars.js'),
editUrl: 'https://github.com/woodpecker-ci/woodpecker/edit/main/docs/',
includeCurrentVersion: true,
lastVersion: '2.7',
lastVersion: '2.8',
onlyIncludeVersions:
process.env.NODE_ENV === 'development' ? ['current', '2.7'] : ['current', '2.7', '2.6', '2.5', '1.0'],
process.env.NODE_ENV === 'development' ? ['current', '2.8'] : ['current', '2.8', '2.7', '2.6', '1.0'],
versions: {
current: {
label: 'Next 🚧',
banner: 'unreleased',
},
'2.8': {
label: '2.8.x',
},
'2.7': {
label: '2.7.x',
banner: 'unmaintained',
},
'2.6': {
label: '2.6.x 💀',
banner: 'unmaintained',
},
'2.5': {
label: '2.5.x 💀',
banner: 'unmaintained',
},
'1.0': {
label: '1.0.x 💀',
banner: 'unmaintained',

View file

@ -17,7 +17,7 @@
"@docusaurus/core": "^3.6.3",
"@docusaurus/plugin-content-blog": "^3.6.3",
"@docusaurus/preset-classic": "^3.6.3",
"@easyops-cn/docusaurus-search-local": "^0.45.0",
"@easyops-cn/docusaurus-search-local": "^0.46.0",
"@mdx-js/react": "^3.1.0",
"@svgr/webpack": "^8.1.0",
"clsx": "^2.1.1",

View file

@ -23,8 +23,8 @@ importers:
specifier: ^3.6.3
version: 3.6.3(@algolia/client-search@5.15.0)(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.7.2)
'@easyops-cn/docusaurus-search-local':
specifier: ^0.45.0
version: 0.45.0(@docusaurus/theme-common@3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
specifier: ^0.46.0
version: 0.46.1(@docusaurus/theme-common@3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@mdx-js/react':
specifier: ^3.1.0
version: 3.1.0(@types/react@18.3.12)(react@18.3.1)
@ -67,7 +67,7 @@ importers:
version: 3.6.3(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@types/node':
specifier: ^22.9.3
version: 22.9.3
version: 22.10.1
'@types/react':
specifier: ^18.3.12
version: 18.3.12
@ -110,19 +110,19 @@ importers:
version: 2.0.3
'@types/node':
specifier: ^22.9.3
version: 22.9.3
version: 22.10.1
axios:
specifier: ^1.7.7
version: 1.7.7
version: 1.7.8
concurrently:
specifier: ^9.1.0
version: 9.1.0
isomorphic-dompurify:
specifier: ^2.16.0
version: 2.16.0
version: 2.18.0
marked:
specifier: ^15.0.2
version: 15.0.2
version: 15.0.3
tslib:
specifier: ^2.8.1
version: 2.8.1
@ -1273,8 +1273,8 @@ packages:
'@easyops-cn/autocomplete.js@0.38.1':
resolution: {integrity: sha512-drg76jS6syilOUmVNkyo1c7ZEBPcPuK+aJA7AksM5ZIIbV57DMHCywiCr+uHyv8BE5jUTU98j/H7gVrkHrWW3Q==}
'@easyops-cn/docusaurus-search-local@0.45.0':
resolution: {integrity: sha512-ccJjeYmBHrv2v8Y9eQnH79S0PEKcogACKkEatEKPcad7usQj/14jA9POUUUYW/yougLSXghwe+uIncbuUBuBFg==}
'@easyops-cn/docusaurus-search-local@0.46.1':
resolution: {integrity: sha512-kgenn5+pctVlJg8s1FOAm9KuZLRZvkBTMMGJvTTcvNTmnFIHVVYzYfA2Eg+yVefzsC8/cSZGKKJ0kLf8I+mQyw==}
engines: {node: '>=12'}
peerDependencies:
'@docusaurus/theme-common': ^2 || ^3
@ -1750,10 +1750,6 @@ packages:
'@types/debug@4.1.12':
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
'@types/dompurify@3.2.0':
resolution: {integrity: sha512-Fgg31wv9QbLDA0SpTOXO3MaxySc4DKGLi8sna4/Utjo4r3ZRPdCt4UQee8BWr+Q5z21yifghREPJGYaEOEIACg==}
deprecated: This is a stub types definition. dompurify provides its own type definitions, so you do not need this installed.
'@types/eslint-scope@3.7.7':
resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==}
@ -1769,8 +1765,8 @@ packages:
'@types/express-serve-static-core@4.19.6':
resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==}
'@types/express-serve-static-core@5.0.1':
resolution: {integrity: sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==}
'@types/express-serve-static-core@5.0.2':
resolution: {integrity: sha512-vluaspfvWEtE4vcSDlKRNer52DvOGrB2xv6diXy6UKyKW0lqZiWHGNApSyxOv+8DE5Z27IzVvE7hNkxg7EXIcg==}
'@types/express@4.17.21':
resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==}
@ -1826,8 +1822,8 @@ packages:
'@types/node@17.0.45':
resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==}
'@types/node@22.9.3':
resolution: {integrity: sha512-F3u1fs/fce3FFk+DAxbxc78DF8x0cY09RRL8GnXLmkJ1jvx3TtPdWoTT5/NiYfI5ASqXBmfqJi9dZ3gxMx4lzw==}
'@types/node@22.10.1':
resolution: {integrity: sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==}
'@types/parse-json@4.0.2':
resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
@ -2104,8 +2100,8 @@ packages:
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
engines: {node: '>= 0.4'}
axios@1.7.7:
resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==}
axios@1.7.8:
resolution: {integrity: sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==}
babel-loader@9.2.1:
resolution: {integrity: sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==}
@ -2228,8 +2224,8 @@ packages:
caniuse-api@3.0.0:
resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==}
caniuse-lite@1.0.30001683:
resolution: {integrity: sha512-iqmNnThZ0n70mNwvxpEC2nBJ037ZHZUoBI5Gorh1Mw6IlEAZujEoU1tXA628iZfzm7R9FvFzxbfdgml82a3k8Q==}
caniuse-lite@1.0.30001684:
resolution: {integrity: sha512-G1LRwLIQjBQoyq0ZJGqGIJUXzJ8irpbjHLpVRXDvBEScFJ9b17sgK6vlx0GAJFE21okD7zXl08rRRUfq6HdoEQ==}
ccount@2.0.1:
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
@ -2343,6 +2339,9 @@ packages:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
comlink@4.4.2:
resolution: {integrity: sha512-OxGdvBmJuNKSCMO4NTl1L47VRp6xn2wG4F/2hYzB6tiCb709otOxtEYCSvK80PtjODfXXZu8ds+Nw5kVCjqd2g==}
comma-separated-tokens@2.0.3:
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
@ -2754,8 +2753,8 @@ packages:
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
engines: {node: '>= 4'}
dompurify@3.2.1:
resolution: {integrity: sha512-NBHEsc0/kzRYQd+AY6HR6B/IgsqzBABrqJbpCDQII/OK6h7B7LXzweZTDsqSW2LkTRpoxf18YUP+YjGySk6B3w==}
dompurify@3.2.2:
resolution: {integrity: sha512-YMM+erhdZ2nkZ4fTNRTSI94mb7VG7uVF5vj5Zde7tImgnhZE3R6YW/IACGIHb2ux+QkEXMhe591N+5jWOmL4Zw==}
domutils@2.8.0:
resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==}
@ -3234,8 +3233,12 @@ packages:
gopd@1.0.1:
resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
got@14.4.4:
resolution: {integrity: sha512-tqiF7eSgTBwQkxb1LxsEpva8TaMYVisbhplrFVmw9GQE3855Z+MH/mnsXLLOkDxR6hZJRFMj5VTAZ8lmTF8ZOA==}
gopd@1.1.0:
resolution: {integrity: sha512-FQoVQnqcdk4hVM4JN1eromaun4iuS34oStkdlLENLdpULsuQcTyXj8w7ayhuUfPwEYZ1ZOooOTT6fdA9Vmx/RA==}
engines: {node: '>= 0.4'}
got@14.4.5:
resolution: {integrity: sha512-sq+uET8TnNKRNnjEOPJzMcxeI0irT8BBNmf+GtZcJpmhYsQM1DSKmCROUjPWKsXZ5HzwD5Cf5/RV+QD9BSTxJg==}
engines: {node: '>=20'}
graceful-fs@4.2.10:
@ -3581,8 +3584,9 @@ packages:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
is-finalizationregistry@1.0.2:
resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==}
is-finalizationregistry@1.1.0:
resolution: {integrity: sha512-qfMdqbAQEwBw78ZyReKnlA8ezmPdb9BemzIIip/JkjaZUhitfXDkkr+3QTboW0JrSXT1QWyYShpvnNHGZ4c4yA==}
engines: {node: '>= 0.4'}
is-fullwidth-code-point@3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
@ -3658,6 +3662,10 @@ packages:
resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==}
engines: {node: '>= 0.4'}
is-regex@1.2.0:
resolution: {integrity: sha512-B6ohK4ZmoftlUe+uvenXSbPJFo6U37BH7oO1B3nQH8f/7h27N56s85MhUtbFJAziz5dcmuR3i8ovUl35zp8pFA==}
engines: {node: '>= 0.4'}
is-regexp@1.0.0:
resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==}
engines: {node: '>=0.10.0'}
@ -3732,8 +3740,8 @@ packages:
resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==}
engines: {node: '>=0.10.0'}
isomorphic-dompurify@2.16.0:
resolution: {integrity: sha512-cXhX2owp8rPxafCr0ywqy2CGI/4ceLNgWkWBEvUz64KTbtg3oRL2ZRqq/zW0pzt4YtDjkHLbwcp/lozpKzAQjg==}
isomorphic-dompurify@2.18.0:
resolution: {integrity: sha512-e0AaROtWPy6ofSTCnUuBvXFidt1eFmrwEbi+Acpz0du6v2H+fq+3svPBn0g/AfBXz24FTWA9ccle7HSFT3HG7A==}
engines: {node: '>=18'}
jest-util@29.7.0:
@ -3920,8 +3928,8 @@ packages:
markdown-table@3.0.4:
resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
marked@15.0.2:
resolution: {integrity: sha512-85RUkoYKIVB21PbMKrnD6aCl9ws+XKEyhJNMbLn206NyD3jbBo7Ec7Wi4Jrsn4dV1a2ng7K/jfkmIN0DNoS41w==}
marked@15.0.3:
resolution: {integrity: sha512-Ai0cepvl2NHnTcO9jYDtcOEtVBNVYR31XnEA3BndO7f5As1wzpcOceSUM8FDkNLJNIODcLpDTWay/qQhqbuMvg==}
engines: {node: '>= 18'}
hasBin: true
@ -4319,8 +4327,8 @@ packages:
peerDependencies:
webpack: ^4.0.0 || ^5.0.0
nwsapi@2.2.13:
resolution: {integrity: sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ==}
nwsapi@2.2.16:
resolution: {integrity: sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==}
oas-kit-common@1.0.8:
resolution: {integrity: sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==}
@ -5163,8 +5171,8 @@ packages:
'@docusaurus/theme-common': ^3.0.0
'@docusaurus/utils': ^3.0.0
reflect.getprototypeof@1.0.6:
resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==}
reflect.getprototypeof@1.0.7:
resolution: {integrity: sha512-bMvFGIUKlc/eSfXNX+aZ+EL95/EgZzuwA0OBPTbZZDEJw/0AkentjMuM1oiRfwHrshqk4RzdgiTg5CcDalXN5g==}
engines: {node: '>= 0.4'}
reftools@1.1.9:
@ -5702,11 +5710,11 @@ packages:
tiny-warning@1.0.3:
resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==}
tldts-core@6.1.63:
resolution: {integrity: sha512-H1XCt54xY+QPbwhTgmxLkepX0MVHu3USfMmejiCOdkMbRcP22Pn2FVF127r/GWXVDmXTRezyF3Ckvhn4Fs6j7Q==}
tldts-core@6.1.65:
resolution: {integrity: sha512-Uq5t0N0Oj4nQSbU8wFN1YYENvMthvwU13MQrMJRspYCGLSAZjAfoBOJki5IQpnBM/WFskxxC/gIOTwaedmHaSg==}
tldts@6.1.63:
resolution: {integrity: sha512-YWwhsjyn9sB/1rOkSRYxvkN/wl5LFM1QDv6F2pVR+pb/jFne4EOBxHfkKVWvDIBEAw9iGOwwubHtQTm0WRT5sQ==}
tldts@6.1.65:
resolution: {integrity: sha512-xU9gLTfAGsADQ2PcWee6Hg8RFAv0DnjMGVJmDnUmI8a9+nYmapMQix4afwrdaCtT+AqP4MaxEzu7cCrYmBPbzQ==}
hasBin: true
to-regex-range@5.0.1:
@ -5760,8 +5768,8 @@ packages:
resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
engines: {node: '>=12.20'}
type-fest@4.27.0:
resolution: {integrity: sha512-3IMSWgP7C5KSQqmo1wjhKrwsvXAtF33jO3QY+Uy++ia7hqvgSK6iXbbg5PbDBc1P2ZbNEDgejOrN4YooXvhwCw==}
type-fest@4.28.0:
resolution: {integrity: sha512-jXMwges/FVbFRe5lTMJZVEZCrO9kI9c8k0PA/z7nF3bo0JSCCLysvokFjNPIUK/itEMas10MQM+AiHoHt/T/XA==}
engines: {node: '>=16'}
type-is@1.6.18:
@ -5795,8 +5803,8 @@ packages:
unbox-primitive@1.0.2:
resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
undici-types@6.19.8:
resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
undici-types@6.20.0:
resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==}
undici@6.21.0:
resolution: {integrity: sha512-BUgJXc752Kou3oOIuU1i+yZZypyZRqNPW0vqoMPl8VaoalSfeR0D8/t4iAS3yirs79SSMTxTag+ZC86uswv+Cw==}
@ -6025,8 +6033,8 @@ packages:
which-boxed-primitive@1.0.2:
resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
which-builtin-type@1.1.4:
resolution: {integrity: sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==}
which-builtin-type@1.2.0:
resolution: {integrity: sha512-I+qLGQ/vucCby4tf5HsLmGueEla4ZhwTBSqaooS+Y0BuxN4Cp+okmGuV+8mXZ84KDI9BA+oklo+RzKg0ONdSUA==}
engines: {node: '>= 0.4'}
which-collection@1.0.2:
@ -7589,7 +7597,7 @@ snapshots:
'@docusaurus/core': 3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(debug@4.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/logger': 3.6.3
'@docusaurus/mdx-loader': 3.6.3(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/plugin-content-docs': 3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/plugin-content-docs': 3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(debug@4.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/theme-common': 3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/types': 3.6.3(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/utils': 3.6.3(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
@ -7629,48 +7637,6 @@ snapshots:
- webpack-cli
'@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(debug@4.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)':
dependencies:
'@docusaurus/core': 3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(debug@4.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/logger': 3.6.3
'@docusaurus/mdx-loader': 3.6.3(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/module-type-aliases': 3.6.3(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/theme-common': 3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(debug@4.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/types': 3.6.3(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/utils': 3.6.3(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/utils-common': 3.6.3(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/utils-validation': 3.6.3(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@types/react-router-config': 5.0.11
combine-promises: 1.2.0
fs-extra: 11.2.0
js-yaml: 4.1.0
lodash: 4.17.21
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
tslib: 2.8.1
utility-types: 3.11.0
webpack: 5.96.1
transitivePeerDependencies:
- '@docusaurus/faster'
- '@mdx-js/react'
- '@parcel/css'
- '@rspack/core'
- '@swc/core'
- '@swc/css'
- acorn
- bufferutil
- csso
- debug
- esbuild
- eslint
- lightningcss
- supports-color
- typescript
- uglify-js
- utf-8-validate
- vue-template-compiler
- webpack-cli
'@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)':
dependencies:
'@docusaurus/core': 3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(debug@4.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/logger': 3.6.3
@ -7902,7 +7868,7 @@ snapshots:
dependencies:
'@docusaurus/core': 3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(debug@4.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/plugin-content-blog': 3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/plugin-content-docs': 3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/plugin-content-docs': 3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(debug@4.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/plugin-content-pages': 3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/plugin-debug': 3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/plugin-google-analytics': 3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
@ -7951,7 +7917,7 @@ snapshots:
'@docusaurus/mdx-loader': 3.6.3(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/module-type-aliases': 3.6.3(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/plugin-content-blog': 3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/plugin-content-docs': 3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/plugin-content-docs': 3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(debug@4.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/plugin-content-pages': 3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/theme-common': 3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/theme-translations': 3.6.3
@ -7995,37 +7961,11 @@ snapshots:
- vue-template-compiler
- webpack-cli
'@docusaurus/theme-common@3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(debug@4.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)':
dependencies:
'@docusaurus/mdx-loader': 3.6.3(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/module-type-aliases': 3.6.3(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/plugin-content-docs': 3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(debug@4.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/utils': 3.6.3(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/utils-common': 3.6.3(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@types/history': 4.7.11
'@types/react': 18.3.12
'@types/react-router-config': 5.0.11
clsx: 2.1.1
parse-numeric-range: 1.3.0
prism-react-renderer: 2.4.0(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
tslib: 2.8.1
utility-types: 3.11.0
transitivePeerDependencies:
- '@swc/core'
- acorn
- esbuild
- supports-color
- typescript
- uglify-js
- webpack-cli
'@docusaurus/theme-common@3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)':
dependencies:
'@docusaurus/mdx-loader': 3.6.3(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/module-type-aliases': 3.6.3(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/plugin-content-docs': 3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/plugin-content-docs': 3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(debug@4.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/utils': 3.6.3(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/utils-common': 3.6.3(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@types/history': 4.7.11
@ -8052,7 +7992,7 @@ snapshots:
'@docsearch/react': 3.8.0(@algolia/client-search@5.15.0)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)
'@docusaurus/core': 3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(debug@4.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/logger': 3.6.3
'@docusaurus/plugin-content-docs': 3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/plugin-content-docs': 3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(debug@4.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/theme-common': 3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/theme-translations': 3.6.3
'@docusaurus/utils': 3.6.3(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
@ -8193,7 +8133,7 @@ snapshots:
cssesc: 3.0.0
immediate: 3.3.0
'@easyops-cn/docusaurus-search-local@0.45.0(@docusaurus/theme-common@3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)':
'@easyops-cn/docusaurus-search-local@0.46.1(@docusaurus/theme-common@3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)':
dependencies:
'@docusaurus/plugin-content-docs': 3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(debug@4.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
'@docusaurus/theme-common': 3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
@ -8205,6 +8145,7 @@ snapshots:
'@node-rs/jieba': 1.10.4
cheerio: 1.0.0
clsx: 1.2.1
comlink: 4.4.2
debug: 4.3.7
fs-extra: 10.1.0
klaw-sync: 6.0.0
@ -8344,7 +8285,7 @@ snapshots:
'@jest/schemas': 29.6.3
'@types/istanbul-lib-coverage': 2.0.6
'@types/istanbul-reports': 3.0.4
'@types/node': 22.9.3
'@types/node': 22.10.1
'@types/yargs': 17.0.33
chalk: 4.1.2
@ -8663,29 +8604,25 @@ snapshots:
'@types/body-parser@1.19.5':
dependencies:
'@types/connect': 3.4.38
'@types/node': 22.9.3
'@types/node': 22.10.1
'@types/bonjour@3.5.13':
dependencies:
'@types/node': 22.9.3
'@types/node': 22.10.1
'@types/connect-history-api-fallback@1.5.4':
dependencies:
'@types/express-serve-static-core': 5.0.1
'@types/node': 22.9.3
'@types/express-serve-static-core': 5.0.2
'@types/node': 22.10.1
'@types/connect@3.4.38':
dependencies:
'@types/node': 22.9.3
'@types/node': 22.10.1
'@types/debug@4.1.12':
dependencies:
'@types/ms': 0.7.34
'@types/dompurify@3.2.0':
dependencies:
dompurify: 3.2.1
'@types/eslint-scope@3.7.7':
dependencies:
'@types/eslint': 9.6.1
@ -8704,14 +8641,14 @@ snapshots:
'@types/express-serve-static-core@4.19.6':
dependencies:
'@types/node': 22.9.3
'@types/node': 22.10.1
'@types/qs': 6.9.17
'@types/range-parser': 1.2.7
'@types/send': 0.17.4
'@types/express-serve-static-core@5.0.1':
'@types/express-serve-static-core@5.0.2':
dependencies:
'@types/node': 22.9.3
'@types/node': 22.10.1
'@types/qs': 6.9.17
'@types/range-parser': 1.2.7
'@types/send': 0.17.4
@ -8739,7 +8676,7 @@ snapshots:
'@types/http-proxy@1.17.15':
dependencies:
'@types/node': 22.9.3
'@types/node': 22.10.1
'@types/istanbul-lib-coverage@2.0.6': {}
@ -8765,13 +8702,13 @@ snapshots:
'@types/node-forge@1.3.11':
dependencies:
'@types/node': 22.9.3
'@types/node': 22.10.1
'@types/node@17.0.45': {}
'@types/node@22.9.3':
'@types/node@22.10.1':
dependencies:
undici-types: 6.19.8
undici-types: 6.20.0
'@types/parse-json@4.0.2': {}
@ -8813,12 +8750,12 @@ snapshots:
'@types/sax@1.2.7':
dependencies:
'@types/node': 22.9.3
'@types/node': 22.10.1
'@types/send@0.17.4':
dependencies:
'@types/mime': 1.3.5
'@types/node': 22.9.3
'@types/node': 22.10.1
'@types/serve-index@1.9.4':
dependencies:
@ -8827,12 +8764,12 @@ snapshots:
'@types/serve-static@1.15.7':
dependencies:
'@types/http-errors': 2.0.4
'@types/node': 22.9.3
'@types/node': 22.10.1
'@types/send': 0.17.4
'@types/sockjs@0.3.36':
dependencies:
'@types/node': 22.9.3
'@types/node': 22.10.1
'@types/stylis@4.2.5': {}
@ -8845,7 +8782,7 @@ snapshots:
'@types/ws@8.5.13':
dependencies:
'@types/node': 22.9.3
'@types/node': 22.10.1
'@types/yargs-parser@21.0.3': {}
@ -9107,7 +9044,7 @@ snapshots:
autoprefixer@10.4.20(postcss@8.4.49):
dependencies:
browserslist: 4.24.2
caniuse-lite: 1.0.30001683
caniuse-lite: 1.0.30001684
fraction.js: 4.3.7
normalize-range: 0.1.2
picocolors: 1.1.1
@ -9118,7 +9055,7 @@ snapshots:
dependencies:
possible-typed-array-names: 1.0.0
axios@1.7.7:
axios@1.7.8:
dependencies:
follow-redirects: 1.15.9(debug@4.3.7)
form-data: 4.0.1
@ -9232,7 +9169,7 @@ snapshots:
browserslist@4.24.2:
dependencies:
caniuse-lite: 1.0.30001683
caniuse-lite: 1.0.30001684
electron-to-chromium: 1.5.64
node-releases: 2.0.18
update-browserslist-db: 1.1.1(browserslist@4.24.2)
@ -9281,11 +9218,11 @@ snapshots:
caniuse-api@3.0.0:
dependencies:
browserslist: 4.24.2
caniuse-lite: 1.0.30001683
caniuse-lite: 1.0.30001684
lodash.memoize: 4.1.2
lodash.uniq: 4.5.0
caniuse-lite@1.0.30001683: {}
caniuse-lite@1.0.30001684: {}
ccount@2.0.1: {}
@ -9407,6 +9344,8 @@ snapshots:
dependencies:
delayed-stream: 1.0.0
comlink@4.4.2: {}
comma-separated-tokens@2.0.3: {}
commander@10.0.1: {}
@ -9862,7 +9801,7 @@ snapshots:
dependencies:
domelementtype: 2.3.0
dompurify@3.2.1:
dompurify@3.2.2:
optionalDependencies:
'@types/trusted-types': 2.0.7
@ -9939,7 +9878,7 @@ snapshots:
is-boolean-object: 1.1.2
is-callable: 1.2.7
is-number-object: 1.0.7
is-regex: 1.1.4
is-regex: 1.2.0
is-string: 1.0.7
is-subset: 0.1.1
lodash.escape: 4.0.1
@ -10158,7 +10097,7 @@ snapshots:
eval@0.1.8:
dependencies:
'@types/node': 22.9.3
'@types/node': 22.10.1
require-like: 0.1.2
eventemitter3@4.0.7: {}
@ -10483,7 +10422,11 @@ snapshots:
dependencies:
get-intrinsic: 1.2.4
got@14.4.4:
gopd@1.1.0:
dependencies:
get-intrinsic: 1.2.4
got@14.4.5:
dependencies:
'@sindresorhus/is': 7.0.1
'@szmarczak/http-timer': 5.0.1
@ -10495,7 +10438,7 @@ snapshots:
lowercase-keys: 3.0.0
p-cancelable: 4.0.1
responselike: 3.0.0
type-fest: 4.27.0
type-fest: 4.28.0
graceful-fs@4.2.10: {}
@ -10911,7 +10854,7 @@ snapshots:
is-extglob@2.1.1: {}
is-finalizationregistry@1.0.2:
is-finalizationregistry@1.1.0:
dependencies:
call-bind: 1.0.7
@ -10967,6 +10910,13 @@ snapshots:
call-bind: 1.0.7
has-tostringtag: 1.0.2
is-regex@1.2.0:
dependencies:
call-bind: 1.0.7
gopd: 1.1.0
has-tostringtag: 1.0.2
hasown: 2.0.2
is-regexp@1.0.0: {}
is-root@2.1.0: {}
@ -11022,10 +10972,9 @@ snapshots:
isobject@3.0.1: {}
isomorphic-dompurify@2.16.0:
isomorphic-dompurify@2.18.0:
dependencies:
'@types/dompurify': 3.2.0
dompurify: 3.2.1
dompurify: 3.2.2
jsdom: 25.0.1
transitivePeerDependencies:
- bufferutil
@ -11036,7 +10985,7 @@ snapshots:
jest-util@29.7.0:
dependencies:
'@jest/types': 29.6.3
'@types/node': 22.9.3
'@types/node': 22.10.1
chalk: 4.1.2
ci-info: 3.9.0
graceful-fs: 4.2.11
@ -11044,13 +10993,13 @@ snapshots:
jest-worker@27.5.1:
dependencies:
'@types/node': 22.9.3
'@types/node': 22.10.1
merge-stream: 2.0.0
supports-color: 8.1.1
jest-worker@29.7.0:
dependencies:
'@types/node': 22.9.3
'@types/node': 22.10.1
jest-util: 29.7.0
merge-stream: 2.0.0
supports-color: 8.1.1
@ -11088,7 +11037,7 @@ snapshots:
http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.5
is-potential-custom-element-name: 1.0.1
nwsapi: 2.2.13
nwsapi: 2.2.16
parse5: 7.2.1
rrweb-cssom: 0.7.1
saxes: 6.0.0
@ -11222,7 +11171,7 @@ snapshots:
markdown-table@3.0.4: {}
marked@15.0.2: {}
marked@15.0.3: {}
marked@4.3.0: {}
@ -11869,7 +11818,7 @@ snapshots:
schema-utils: 3.3.0
webpack: 5.96.1
nwsapi@2.2.13: {}
nwsapi@2.2.16: {}
oas-kit-common@1.0.8:
dependencies:
@ -12001,7 +11950,7 @@ snapshots:
package-json@8.1.1:
dependencies:
got: 14.4.4
got: 14.4.5
registry-auth-token: 5.0.2
registry-url: 6.0.1
semver: 7.6.3
@ -12820,7 +12769,7 @@ snapshots:
classnames: 2.5.1
core-js: 3.39.0
decko: 1.2.0
dompurify: 3.2.1
dompurify: 3.2.2
eventemitter3: 5.0.1
json-pointer: 0.6.2
lunr: 2.3.9
@ -12866,15 +12815,15 @@ snapshots:
- supports-color
- webpack
reflect.getprototypeof@1.0.6:
reflect.getprototypeof@1.0.7:
dependencies:
call-bind: 1.0.7
define-properties: 1.2.1
es-abstract: 1.23.5
es-errors: 1.3.0
get-intrinsic: 1.2.4
globalthis: 1.0.4
which-builtin-type: 1.1.4
gopd: 1.0.1
which-builtin-type: 1.2.0
reftools@1.1.9: {}
@ -13523,11 +13472,11 @@ snapshots:
tiny-warning@1.0.3: {}
tldts-core@6.1.63: {}
tldts-core@6.1.65: {}
tldts@6.1.63:
tldts@6.1.65:
dependencies:
tldts-core: 6.1.63
tldts-core: 6.1.65
to-regex-range@5.0.1:
dependencies:
@ -13539,7 +13488,7 @@ snapshots:
tough-cookie@5.0.0:
dependencies:
tldts: 6.1.63
tldts: 6.1.65
tr46@0.0.3: {}
@ -13563,7 +13512,7 @@ snapshots:
type-fest@2.19.0: {}
type-fest@4.27.0: {}
type-fest@4.28.0: {}
type-is@1.6.18:
dependencies:
@ -13592,7 +13541,7 @@ snapshots:
gopd: 1.0.1
has-proto: 1.0.3
is-typed-array: 1.1.13
reflect.getprototypeof: 1.0.6
reflect.getprototypeof: 1.0.7
typed-array-length@1.0.7:
dependencies:
@ -13601,7 +13550,7 @@ snapshots:
gopd: 1.0.1
is-typed-array: 1.1.13
possible-typed-array-names: 1.0.0
reflect.getprototypeof: 1.0.6
reflect.getprototypeof: 1.0.7
typedarray-to-buffer@3.1.5:
dependencies:
@ -13616,7 +13565,7 @@ snapshots:
has-symbols: 1.0.3
which-boxed-primitive: 1.0.2
undici-types@6.19.8: {}
undici-types@6.20.0: {}
undici@6.21.0: {}
@ -13930,13 +13879,14 @@ snapshots:
is-string: 1.0.7
is-symbol: 1.0.4
which-builtin-type@1.1.4:
which-builtin-type@1.2.0:
dependencies:
call-bind: 1.0.7
function.prototype.name: 1.1.6
has-tostringtag: 1.0.2
is-async-function: 2.0.0
is-date-object: 1.0.5
is-finalizationregistry: 1.0.2
is-finalizationregistry: 1.1.0
is-generator-function: 1.0.10
is-regex: 1.1.4
is-weakref: 1.0.2

View file

@ -1,8 +1,8 @@
# Awesome Woodpecker
A curated list of awesome things related to Woodpecker CI.
A curated list of assets (tools, projects, blog posts) related to Woodpecker CI.
If you have some missing resources, please feel free to [open a pull-request](https://github.com/woodpecker-ci/woodpecker/edit/main/docs/docs/92-awesome.md) and add them.
If you want to add a new entry, open a [pull-request](https://github.com/woodpecker-ci/woodpecker/edit/main/docs/docs/92-awesome.md).
## Official Resources
@ -52,7 +52,7 @@ If you have some missing resources, please feel free to [open a pull-request](ht
- [Quest For CICD - WoodpeckerCI](https://omaramin.me/posts/woodpecker/)
- [Getting started with Woodpecker CI](https://systeemkabouter.eu/getting-started-with-woodpecker-ci.html)
- [Installing gitea and woodpecker using binary packages](https://neelex.com/2023/03/26/Installing-gitea-using-binary-packages/)
- [Deploying mdbook to codeberg pages using woodpecker CI](https://www.markpitblado.me/blog/deploying-mdbook-to-codeberg-pages-using-woodpecker-ci/)
- [Deploying mdbook to codeberg pages using Woodpecker CI](https://www.markpitblado.me/blog/deploying-mdbook-to-codeberg-pages-using-woodpecker-ci/)
- [Deploy a Fly app with Woodpecker CI](https://joeroe.io/2024/01/09/deploy-fly-woodpecker-ci.html)
- [Ansible - using Woodpecker as an alternative to Semaphore](https://pat-s.me/ansible-using-woodpecker-as-an-alternative-to-semaphore/)
- [Simple selfhosted CI/CD with Woodpecker](https://xyquadrat.ch/blog/simple-ci-with-woodpecker/)

View file

@ -33,6 +33,17 @@ This will be the next version of Woodpecker.
- Removed old API routes: `registry/` -> `registries`, `/authorize/token`
- Replaced `registry` command with `repo registry` in cli
- Deprecated `secrets`, use `environment` with `from_secret`
- CLI commands got restructured to provide a simplified structure:
- `woodpecker-cli secret [add|rm|...] --global` is now `woodpecker-cli admin secret [add|rm|...]`
- `woodpecker-cli user` is now `woodpecker-cli admin user`
- `woodpecker-cli log-level` is now `woodpecker-cli admin log-level`
- `woodpecker-cli secret [add|rm|...] --organization` is now `woodpecker-cli org secret [add|rm|...]`
- `woodpecker-cli deploy` is now `woodpecker-cli pipeline deploy`
- `woodpecker-cli log` is now `woodpecker-cli pipeline log`
- `woodpecker-cli cron` is now `woodpecker-cli repo cron`
- `woodpecker-cli secret [add|rm|...] --repository` is now `woodpecker-cli repo secret [add|rm|...]`
- `woodpecker-cli pipeline logs` is now `woodpecker-cli pipeline log show`
- `woodpecker-cli [registry|secret|...] info` is now `woodpecker-cli [registry|secret|...] show`
## Admin migrations

View file

@ -33,6 +33,7 @@ Here you can find documentation for previous versions of Woodpecker.
| | | |
| ------- | ---------- | ------------------------------------------------------------------------------------- |
| 2.7.3 | 2024-11-28 | [Documentation](https://github.com/woodpecker-ci/woodpecker/tree/v2.7.3/docs/docs/) |
| 2.7.2 | 2024-11-03 | [Documentation](https://github.com/woodpecker-ci/woodpecker/tree/v2.7.2/docs/docs/) |
| 2.7.1 | 2024-09-07 | [Documentation](https://github.com/woodpecker-ci/woodpecker/tree/v2.7.1/docs/docs/) |
| 2.7.0 | 2024-07-18 | [Documentation](https://github.com/woodpecker-ci/woodpecker/tree/v2.7.0/docs/docs/) |

View file

@ -1,89 +0,0 @@
# Welcome to Woodpecker
Woodpecker is a simple, yet powerful CI/CD engine with great extensibility. It focuses on executing pipelines inside [containers](https://opencontainers.org/).
If you are already using containers in your daily workflow, you'll for sure love Woodpecker.
![woodpecker](woodpecker.png)
## `.woodpecker.yaml`
- Place your pipeline in a file named `.woodpecker.yaml` in your repository
- Pipeline steps can be named as you like
- Run any command in the commands section
```yaml title=".woodpecker.yaml"
steps:
- name: build
image: debian
commands:
- echo "This is the build step"
- name: a-test-step
image: debian
commands:
- echo "Testing.."
```
### Steps are containers
- Define any container image as context
- either use your own and install the needed tools in a custom image
- or search for available images that are already tailored for your needs in image registries like [Docker Hub](https://hub.docker.com/search?type=image)
- List the commands that should be executed in the container
```diff
steps:
- name: build
- image: debian
+ image: mycompany/image-with-awscli
commands:
- aws help
```
### File changes are incremental
- Woodpecker clones the source code in the beginning
- File changes are persisted throughout individual steps as the same volume is being mounted in all steps
```yaml title=".woodpecker.yaml"
steps:
- name: build
image: debian
commands:
- touch myfile
- name: a-test-step
image: debian
commands:
- cat myfile
```
## Plugins are straightforward
- If you copy the same shell script from project to project
- Pack it into a plugin instead
- And make the yaml declarative
- Plugins are Docker images with your script as an entrypoint
```dockerfile title="Dockerfile"
FROM laszlocloud/kubectl
COPY deploy /usr/local/deploy
ENTRYPOINT ["/usr/local/deploy"]
```
```bash title="deploy"
kubectl apply -f $PLUGIN_TEMPLATE
```
```yaml title=".woodpecker.yaml"
steps:
- name: deploy-to-k8s
image: laszlocloud/my-k8s-plugin
settings:
template: config/k8s/service.yaml
```
See [plugin docs](./20-usage/51-plugins/51-overview.md).
## Continue reading
- [Create a Woodpecker pipeline for your repository](./20-usage/10-intro.md)
- [Setup your own Woodpecker instance](./30-administration/00-deployment/00-overview.md)

View file

@ -1,72 +0,0 @@
# Getting started
## Repository Activation
To activate your project navigate to your account settings. You will see a list of repositories which can be activated with a simple toggle. When you activate your repository, Woodpecker automatically adds webhooks to your forge (e.g. GitHub, Gitea, ...).
Webhooks are used to trigger pipeline executions. When you push code to your repository, open a pull request, or create a tag, your forge will automatically send a webhook to Woodpecker which will in turn trigger the pipeline execution.
![repository list](repo-list.png)
## Required Permissions
The user who enables a repo in Woodpecker must have `Admin` rights on that repo, so that Woodpecker can add the webhook.
:::note
Note that manually creating webhooks yourself is not possible.
This is because webhooks are signed using a per-repository secret key which is not exposed to end users.
:::
## Configuration
To configure your pipeline you must create a `.woodpecker.yaml` file in the root of your repository. The `.woodpecker.yaml` file is used to define your pipeline steps.
:::note
We support most of YAML 1.2, but preserve some behavior from 1.1 for backward compatibility.
Read more at: [https://github.com/go-yaml/yaml](https://github.com/go-yaml/yaml/tree/v3)
:::
Example pipeline configuration:
```yaml
steps:
- name: build
image: golang
commands:
- go get
- go build
- go test
services:
- name: postgres
image: postgres:9.4.5
environment:
- POSTGRES_USER=myapp
```
Example pipeline configuration with multiple, serial steps:
```yaml
steps:
- name: backend
image: golang
commands:
- go get
- go build
- go test
- name: frontend
image: node:6
commands:
- npm install
- npm test
- name: notify
image: plugins/slack
channel: developers
username: woodpecker
```
## Execution
To trigger your first pipeline execution you can push code to your repository, open a pull request, or push a tag. Any of these events triggers a webhook from your forge and execute your pipeline.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View file

@ -1,90 +0,0 @@
# Deployment
A Woodpecker deployment consists of two parts:
- A server which is the heart of Woodpecker and ships the web interface.
- Next to one server, you can deploy any number of agents which will run the pipelines.
Each agent is able to process one pipeline step by default.
If you have four agents installed and connected to the Woodpecker server, your system will process four workflows in parallel.
:::tip
You can add more agents to increase the number of parallel workflows or set the agent's `WOODPECKER_MAX_WORKFLOWS=1` environment variable to increase the number of parallel workflows for that agent.
:::
## Which version of Woodpecker should I use?
Woodpecker is having two different kinds of releases: **stable** and **next**.
### Stable releases
We release a new version every four weeks and will release the current state of the `main` branch.
If there are security fixes or critical bug fixes, we'll release them directly.
There are no backports or similar.
#### Versioning
We use [Semantic Versioning](https://semver.org/) to be able,
to communicate when admins have to do manual migration steps and when they can just bump versions up.
#### Breaking changes
As of semver guidelines, breaking changes will be released as a major version. We will hold back
breaking changes to not release many majors each containing just a few breaking changes.
Prior to the release of a major version, a release candidate (RC) will be published to allow easy testing,
the actual release will be about a week later.
## Hardware Requirements
Below are minimal resources requirements for Woodpecker components itself:
| Component | Memory | CPU |
| --------- | ------ | --- |
| Server | 200 MB | 1 |
| Agent | 32 MB | 1 |
Note, that those values do not include the operating system or workload (pipelines execution) resources consumption.
In addition you need at least some kind of database which requires additional resources depending on the selected database system.
## Installation
You can install Woodpecker on multiple ways:
- Using [docker-compose](./10-docker-compose.md) with the official [container images](./10-docker-compose.md#docker-images)
- Using [Kubernetes](./20-kubernetes.md) via the Woodpecker Helm chart
- Using binaries, DEBs or RPMs you can download from [latest release](https://github.com/woodpecker-ci/woodpecker/releases/latest)
## Authentication
Authentication is done using OAuth and is delegated to your forge which is configured using environment variables.
See the complete reference for all supported forges [here](../11-forges/11-overview.md).
## Database
By default Woodpecker uses a SQLite database which requires zero installation or configuration. See the [database settings](../30-database.md) page to further configure it or use MySQL or Postgres.
## SSL
Woodpecker supports SSL configuration by using Let's encrypt or by using own certificates. See the [SSL guide](../60-ssl.md). You can also put it behind a [reverse proxy](#behind-a-proxy)
## Metrics
A [Prometheus endpoint](../90-prometheus.md) is exposed.
## Behind a proxy
See the [proxy guide](../70-proxy.md) if you want to see a setup behind Apache, Nginx, Caddy or ngrok.
In the case you need to use Woodpecker with a URL path prefix (like: <https://example.org/woodpecker/>), add the root path to [`WOODPECKER_HOST`](../10-server-config.md#woodpecker_host).
## Third-party installation methods
:::info
These installation methods are not officially supported. If you experience issues with them, please open issues in the specific repositories.
:::
- Using [NixOS](./30-nixos.md) via the [NixOS module](https://search.nixos.org/options?channel=unstable&size=200&sort=relevance&query=woodpecker)
- [Using YunoHost](https://apps.yunohost.org/app/woodpecker)
- [On Cloudron](https://www.cloudron.io/store/org.woodpecker_ci.cloudronapp.html)

View file

@ -1,13 +0,0 @@
# Forges
## Supported features
| Feature | [GitHub](20-github.md) | [Gitea / Forgejo](30-gitea.md) | [Gitlab](40-gitlab.md) | [Bitbucket](50-bitbucket.md) | [Bitbucket Datacenter](60-bitbucket_datacenter.md) |
| ------------------------------------------------------------- | :--------------------: | :----------------------------: | :--------------------: | :--------------------------: | :------------------------------------------------: |
| Event: Push | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Event: Tag | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Event: Pull-Request | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Event: Release | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :x: |
| Event: Deploy | :white_check_mark: | :x: | :x: | :x: | :x: |
| [Multiple workflows](../../20-usage/25-workflows.md) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| [when.path filter](../../20-usage/20-workflow-syntax.md#path) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :x: |

View file

@ -0,0 +1,26 @@
# Welcome to Woodpecker
Woodpecker is a CI/CD tool. It is designed to be lightweight, simple to use and fast. Before we dive into the details, let's have a look at some of the basics.
## Have you ever heard of CI/CD or pipelines?
Don't worry if you haven't. We'll guide you through the basics. CI/CD stands for Continuous Integration and Continuous Deployment. It's basically like a conveyor belt that moves your code from development to production doing all kinds of
checks, tests and routines along the way. A typical pipeline might include the following steps:
1. Running tests
2. Building your application
3. Deploying your application
[Have a deeper look into the idea of CI/CD](https://www.redhat.com/en/topics/devops/what-is-ci-cd)
## Do you know containers?
If you are already using containers in your daily workflow, you'll for sure love Woodpecker. If not yet, you'll be amazed how easy it is to get started with [containers](https://opencontainers.org/).
## Already have access to a Woodpecker instace?
Then you might want to jump directly into it and [start creating your first pipelines](../20-usage/10-intro.md).
## Want to start from scratch and deploy your own Woodpecker instance?
Woodpecker is [pretty lightweight](../30-administration/00-getting-started.md#hardware-requirements) and will even run on your Raspberry Pi. You can follow the [deployment guide](../30-administration/00-getting-started.md) to set up your own Woodpecker instance.

View file

@ -0,0 +1,109 @@
# Your first pipeline
Let's get started and create your first pipeline.
## 1. Repository Activation
To activate your repository in Woodpecker navigate to the repository list and `New repository`. You will see a list of repositories from your forge (GitHub, Gitlab, ...) which can be activated with a simple click.
![new repository list](repo-new.png)
To enable a repository in Woodpecker you must have `Admin` rights on that repository, so that Woodpecker can add something
that is called a webhook (Woodpecker needs it to know about actions like pushes, pull requests, tags, etc.).
## 2. Define first workflow
After enabling a repository Woodpecker will listen for changes in your repository. When a change is detected, Woodpecker will check for a pipeline configuration. So let's create a file at `.woodpecker/my-first-workflow.yaml` inside your repository:
```yaml title=".woodpecker/my-first-workflow.yaml"
when:
- event: push
branch: main
steps:
- name: build
image: debian
commands:
- echo "This is the build step"
- echo "binary-data-123" > executable
- name: a-test-step
image: golang:1.16
commands:
- echo "Testing ..."
- ./executable
```
**So what did we do here?**
1. We defined your first workflow file `my-first-workflow.yaml`.
2. This workflow will be executed when a push event happens on the `main` branch,
because we added a filter using the `when` section:
```diff
+ when:
+ - event: push
+ branch: main
...
```
3. We defined two steps: `build` and `a-test-step`
The steps are executed in the order they are defined, so `build` will be executed first and then `a-test-step`.
In the `build` step we use the `debian` image and build a "binary file" called `executable`.
In the `a-test-step` we use the `golang:1.16` image and run the `executable` file to test it.
You can use any image from registries like the [Docker Hub](https://hub.docker.com/search?type=image) you have access to:
```diff
steps:
- name: build
- image: debian
+ image: mycompany/image-with-awscli
commands:
- aws help
```
## 3. Push the file and trigger first pipeline
If you push this file to your repository now, Woodpecker will already execute your first pipeline.
You can check the pipeline execution in the Woodpecker UI by navigating to the `Pipelines` section of your repository.
![pipeline view](./pipeline.png)
As you probably noticed, there is another step in called `clone` which is executed before your steps. This step clones your repository into a folder called `workspace` which is available throughout all steps.
This for example allows the first step to build your application using your source code and as the second step will receive
the same workspace it can use the previously built binary and test it.
## 4. Use a plugin for reusable tasks
Sometimes you have some tasks that you need to do in every project. For example, deploying to Kubernetes or sending a Slack message. Therefore you can use one of the [official and community plugins](/plugins) or simply [create your own](./51-plugins/20-creating-plugins.md).
If you want to get a Slack notification after your pipeline has finished, you can add a Slack plugin to your pipeline:
```yaml
---
- name: notify me on Slack
image: plugins/slack
settings:
channel: developers
username: woodpecker
password:
from_secret: slack_token
when:
status: [success, failure] # This will execute the step on success and failure
```
To configure a plugin you can use the `settings` section.
Sometime you need to provide secrets to the plugin. You can do this by using the `from_secret` key. The secret must be defined in the Woodpecker UI. You can find more information about secrets [here](./40-secrets.md).
Similar to the `when` section at the top of the file which is for the complete workflow, you can use the `when` section for each step to define when a step should be executed.
Learn more about [plugins](./51-plugins/51-overview.md).
As you now have a basic understanding of how to create a pipeline, you can dive deeper into the [workflow syntax](./20-workflow-syntax.md) and [plugins](./51-plugins/51-overview.md).

View file

@ -0,0 +1,37 @@
# Troubleshooting
## How to debug clone issues
(And what to do with an error message like `fatal: could not read Username for 'https://<url>': No such device or address`)
This error can have multiple causes. If you use internal repositories you might have to enable `WOODPECKER_AUTHENTICATE_PUBLIC_REPOS`:
```ini
WOODPECKER_AUTHENTICATE_PUBLIC_REPOS=true
```
If that does not work, try to make sure the container can reach your git server. In order to do that disable git checkout and make the container "hang":
```yaml
skip_clone: true
steps:
build:
image: debian:stable-backports
commands:
- apt update
- apt install -y inetutils-ping wget
- ping -c 4 git.example.com
- wget git.example.com
- sleep 9999999
```
Get the container id using `docker ps` and copy the id from the first column. Enter the container with: `docker exec -it 1234asdf bash` (replace `1234asdf` with the docker id). Then try to clone the git repository with the commands from the failing pipeline:
```bash
git init
git remote add origin https://git.example.com/username/repo.git
git fetch --no-tags origin +refs/heads/branch:
```
(replace the url AND the branch with the correct values, use your username and password as log in values)

View file

@ -1,13 +1,5 @@
# Terminology
## Woodpecker architecture
![Woodpecker architecture](architecture.svg)
## Pipeline, workflow & step
![Relation between pipelines, workflows and steps](pipeline-workflow-step.svg)
## Glossary
- **Woodpecker CI**: The project name around Woodpecker.
@ -33,6 +25,14 @@
- **Status**: Status refers to the outcome of a step or [workflow][Workflow] after it has been executed, determined by the internal command exit code. At the end of a [workflow][Workflow], its status is sent to the [forge][Forge].
- **Service extension**: Some parts of Woodpecker internal services like secrets storage or config fetcher can be replaced through service extensions.
## Woodpecker architecture
![Woodpecker architecture](architecture.svg)
## Pipeline, workflow & step
![Relation between pipelines, workflows and steps](pipeline-workflow-step.svg)
## Pipeline events
- `push`: A push event is triggered when a commit is pushed to a branch.

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