mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-25 19:31:05 +00:00
Global and organization registries (#1672)
Co-authored-by: Anbraten <6918444+anbraten@users.noreply.github.com>
This commit is contained in:
parent
e5f3e67bf2
commit
28e982fffb
65 changed files with 3260 additions and 269 deletions
30
cli/admin/admin.go
Normal file
30
cli/admin/admin.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
// Copyright 2024 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 admin
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/cli/admin/registry"
|
||||
)
|
||||
|
||||
// Command exports the admin command set.
|
||||
var Command = &cli.Command{
|
||||
Name: "admin",
|
||||
Usage: "administer server settings",
|
||||
Subcommands: []*cli.Command{
|
||||
registry.Command,
|
||||
},
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 Woodpecker Authors
|
||||
// Copyright 2024 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -21,7 +21,7 @@ import (
|
|||
// Command exports the registry command set.
|
||||
var Command = &cli.Command{
|
||||
Name: "registry",
|
||||
Usage: "manage registries",
|
||||
Usage: "manage global registries",
|
||||
Subcommands: []*cli.Command{
|
||||
registryCreateCmd,
|
||||
registryDeleteCmd,
|
75
cli/admin/registry/registry_add.go
Normal file
75
cli/admin/registry/registry_add.go
Normal file
|
@ -0,0 +1,75 @@
|
|||
// Copyright 2024 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 registry
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
|
||||
)
|
||||
|
||||
var registryCreateCmd = &cli.Command{
|
||||
Name: "add",
|
||||
Usage: "adds a registry",
|
||||
Action: registryCreate,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "hostname",
|
||||
Usage: "registry hostname",
|
||||
Value: "docker.io",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "username",
|
||||
Usage: "registry username",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "password",
|
||||
Usage: "registry password",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func registryCreate(c *cli.Context) error {
|
||||
var (
|
||||
hostname = c.String("hostname")
|
||||
username = c.String("username")
|
||||
password = c.String("password")
|
||||
)
|
||||
|
||||
client, err := internal.NewClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
registry := &woodpecker.Registry{
|
||||
Address: hostname,
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
if strings.HasPrefix(registry.Password, "@") {
|
||||
path := strings.TrimPrefix(registry.Password, "@")
|
||||
out, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
registry.Password = string(out)
|
||||
}
|
||||
|
||||
_, err = client.GlobalRegistryCreate(registry)
|
||||
return err
|
||||
}
|
62
cli/admin/registry/registry_info.go
Normal file
62
cli/admin/registry/registry_info.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
// Copyright 2024 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 registry
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"os"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
|
||||
)
|
||||
|
||||
var registryInfoCmd = &cli.Command{
|
||||
Name: "info",
|
||||
Usage: "display registry info",
|
||||
Action: registryInfo,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "hostname",
|
||||
Usage: "registry hostname",
|
||||
Value: "docker.io",
|
||||
},
|
||||
common.FormatFlag(tmplRegistryList, true),
|
||||
},
|
||||
}
|
||||
|
||||
func registryInfo(c *cli.Context) error {
|
||||
var (
|
||||
hostname = c.String("hostname")
|
||||
format = c.String("format") + "\n"
|
||||
)
|
||||
|
||||
client, err := internal.NewClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
registry, err := client.GlobalRegistry(hostname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmpl, err := template.New("_").Parse(format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tmpl.Execute(os.Stdout, registry)
|
||||
}
|
65
cli/admin/registry/registry_list.go
Normal file
65
cli/admin/registry/registry_list.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
// Copyright 2024 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 registry
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"os"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
|
||||
)
|
||||
|
||||
var registryListCmd = &cli.Command{
|
||||
Name: "ls",
|
||||
Usage: "list registries",
|
||||
Action: registryList,
|
||||
Flags: []cli.Flag{
|
||||
common.FormatFlag(tmplRegistryList, true),
|
||||
},
|
||||
}
|
||||
|
||||
func registryList(c *cli.Context) error {
|
||||
format := c.String("format") + "\n"
|
||||
|
||||
client, err := internal.NewClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
list, err := client.GlobalRegistryList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmpl, err := template.New("_").Parse(format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, registry := range list {
|
||||
if err := tmpl.Execute(os.Stdout, registry); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Template for registry list information.
|
||||
var tmplRegistryList = "\x1b[33m{{ .Address }} \x1b[0m" + `
|
||||
Username: {{ .Username }}
|
||||
Email: {{ .Email }}
|
||||
`
|
45
cli/admin/registry/registry_rm.go
Normal file
45
cli/admin/registry/registry_rm.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
// Copyright 2024 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 registry
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
|
||||
)
|
||||
|
||||
var registryDeleteCmd = &cli.Command{
|
||||
Name: "rm",
|
||||
Usage: "remove a registry",
|
||||
Action: registryDelete,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "hostname",
|
||||
Usage: "registry hostname",
|
||||
Value: "docker.io",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func registryDelete(c *cli.Context) error {
|
||||
hostname := c.String("hostname")
|
||||
|
||||
client, err := internal.NewClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return client.GlobalRegistryDelete(hostname)
|
||||
}
|
78
cli/admin/registry/registry_set.go
Normal file
78
cli/admin/registry/registry_set.go
Normal file
|
@ -0,0 +1,78 @@
|
|||
// Copyright 2024 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 registry
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"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 registryUpdateCmd = &cli.Command{
|
||||
Name: "update",
|
||||
Usage: "update a registry",
|
||||
Action: registryUpdate,
|
||||
Flags: []cli.Flag{
|
||||
common.OrgFlag,
|
||||
&cli.StringFlag{
|
||||
Name: "hostname",
|
||||
Usage: "registry hostname",
|
||||
Value: "docker.io",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "username",
|
||||
Usage: "registry username",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "password",
|
||||
Usage: "registry password",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func registryUpdate(c *cli.Context) error {
|
||||
var (
|
||||
hostname = c.String("hostname")
|
||||
username = c.String("username")
|
||||
password = c.String("password")
|
||||
)
|
||||
|
||||
client, err := internal.NewClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
registry := &woodpecker.Registry{
|
||||
Address: hostname,
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
if strings.HasPrefix(registry.Password, "@") {
|
||||
path := strings.TrimPrefix(registry.Password, "@")
|
||||
out, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
registry.Password = string(out)
|
||||
}
|
||||
|
||||
_, err = client.GlobalRegistryUpdate(registry)
|
||||
return err
|
||||
}
|
30
cli/org/org.go
Normal file
30
cli/org/org.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
// Copyright 2024 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 org
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/cli/org/registry"
|
||||
)
|
||||
|
||||
// Command exports the org command set.
|
||||
var Command = &cli.Command{
|
||||
Name: "org",
|
||||
Usage: "manage organizations",
|
||||
Subcommands: []*cli.Command{
|
||||
registry.Command,
|
||||
},
|
||||
}
|
60
cli/org/registry/registry.go
Normal file
60
cli/org/registry/registry.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
// Copyright 2024 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 registry
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
|
||||
)
|
||||
|
||||
// Command exports the registry command set.
|
||||
var Command = &cli.Command{
|
||||
Name: "registry",
|
||||
Usage: "manage organization registries",
|
||||
Subcommands: []*cli.Command{
|
||||
registryCreateCmd,
|
||||
registryDeleteCmd,
|
||||
registryUpdateCmd,
|
||||
registryInfoCmd,
|
||||
registryListCmd,
|
||||
},
|
||||
}
|
||||
|
||||
func parseTargetArgs(client woodpecker.Client, c *cli.Context) (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
|
||||
}
|
83
cli/org/registry/registry_add.go
Normal file
83
cli/org/registry/registry_add.go
Normal file
|
@ -0,0 +1,83 @@
|
|||
// Copyright 2024 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 registry
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"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 registryCreateCmd = &cli.Command{
|
||||
Name: "add",
|
||||
Usage: "adds a registry",
|
||||
ArgsUsage: "[org-id|org-full-name]",
|
||||
Action: registryCreate,
|
||||
Flags: []cli.Flag{
|
||||
common.OrgFlag,
|
||||
&cli.StringFlag{
|
||||
Name: "hostname",
|
||||
Usage: "registry hostname",
|
||||
Value: "docker.io",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "username",
|
||||
Usage: "registry username",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "password",
|
||||
Usage: "registry password",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func registryCreate(c *cli.Context) error {
|
||||
var (
|
||||
hostname = c.String("hostname")
|
||||
username = c.String("username")
|
||||
password = c.String("password")
|
||||
)
|
||||
|
||||
client, err := internal.NewClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
registry := &woodpecker.Registry{
|
||||
Address: hostname,
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
if strings.HasPrefix(registry.Password, "@") {
|
||||
path := strings.TrimPrefix(registry.Password, "@")
|
||||
out, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
registry.Password = string(out)
|
||||
}
|
||||
|
||||
orgID, err := parseTargetArgs(client, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = client.OrgRegistryCreate(orgID, registry)
|
||||
return err
|
||||
}
|
69
cli/org/registry/registry_info.go
Normal file
69
cli/org/registry/registry_info.go
Normal file
|
@ -0,0 +1,69 @@
|
|||
// Copyright 2024 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 registry
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"os"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
|
||||
)
|
||||
|
||||
var registryInfoCmd = &cli.Command{
|
||||
Name: "info",
|
||||
Usage: "display registry info",
|
||||
ArgsUsage: "[org-id|org-full-name]",
|
||||
Action: registryInfo,
|
||||
Flags: []cli.Flag{
|
||||
common.OrgFlag,
|
||||
&cli.StringFlag{
|
||||
Name: "hostname",
|
||||
Usage: "registry hostname",
|
||||
Value: "docker.io",
|
||||
},
|
||||
common.FormatFlag(tmplRegistryList, true),
|
||||
},
|
||||
}
|
||||
|
||||
func registryInfo(c *cli.Context) error {
|
||||
var (
|
||||
hostname = c.String("hostname")
|
||||
format = c.String("format") + "\n"
|
||||
)
|
||||
|
||||
client, err := internal.NewClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
orgID, err := parseTargetArgs(client, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
registry, err := client.OrgRegistry(orgID, hostname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmpl, err := template.New("_").Parse(format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tmpl.Execute(os.Stdout, registry)
|
||||
}
|
72
cli/org/registry/registry_list.go
Normal file
72
cli/org/registry/registry_list.go
Normal file
|
@ -0,0 +1,72 @@
|
|||
// Copyright 2024 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 registry
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"os"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
|
||||
)
|
||||
|
||||
var registryListCmd = &cli.Command{
|
||||
Name: "ls",
|
||||
Usage: "list registries",
|
||||
ArgsUsage: "[org-id|org-full-name]",
|
||||
Action: registryList,
|
||||
Flags: []cli.Flag{
|
||||
common.OrgFlag,
|
||||
common.FormatFlag(tmplRegistryList, true),
|
||||
},
|
||||
}
|
||||
|
||||
func registryList(c *cli.Context) error {
|
||||
format := c.String("format") + "\n"
|
||||
|
||||
client, err := internal.NewClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
orgID, err := parseTargetArgs(client, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
list, err := client.OrgRegistryList(orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmpl, err := template.New("_").Parse(format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, registry := range list {
|
||||
if err := tmpl.Execute(os.Stdout, registry); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Template for registry list information.
|
||||
var tmplRegistryList = "\x1b[33m{{ .Address }} \x1b[0m" + `
|
||||
Username: {{ .Username }}
|
||||
Email: {{ .Email }}
|
||||
`
|
53
cli/org/registry/registry_rm.go
Normal file
53
cli/org/registry/registry_rm.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
// Copyright 2024 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 registry
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
|
||||
)
|
||||
|
||||
var registryDeleteCmd = &cli.Command{
|
||||
Name: "rm",
|
||||
Usage: "remove a registry",
|
||||
ArgsUsage: "[org-id|org-full-name]",
|
||||
Action: registryDelete,
|
||||
Flags: []cli.Flag{
|
||||
common.OrgFlag,
|
||||
&cli.StringFlag{
|
||||
Name: "hostname",
|
||||
Usage: "registry hostname",
|
||||
Value: "docker.io",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func registryDelete(c *cli.Context) error {
|
||||
hostname := c.String("hostname")
|
||||
|
||||
client, err := internal.NewClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
orgID, err := parseTargetArgs(client, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return client.OrgRegistryDelete(orgID, hostname)
|
||||
}
|
84
cli/org/registry/registry_set.go
Normal file
84
cli/org/registry/registry_set.go
Normal file
|
@ -0,0 +1,84 @@
|
|||
// Copyright 2024 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 registry
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"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 registryUpdateCmd = &cli.Command{
|
||||
Name: "update",
|
||||
Usage: "update a registry",
|
||||
ArgsUsage: "[org-id|org-full-name]",
|
||||
Action: registryUpdate,
|
||||
Flags: []cli.Flag{
|
||||
common.OrgFlag,
|
||||
&cli.StringFlag{
|
||||
Name: "hostname",
|
||||
Usage: "registry hostname",
|
||||
Value: "docker.io",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "username",
|
||||
Usage: "registry username",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "password",
|
||||
Usage: "registry password",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func registryUpdate(c *cli.Context) error {
|
||||
var (
|
||||
hostname = c.String("hostname")
|
||||
username = c.String("username")
|
||||
password = c.String("password")
|
||||
)
|
||||
|
||||
client, err := internal.NewClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
registry := &woodpecker.Registry{
|
||||
Address: hostname,
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
if strings.HasPrefix(registry.Password, "@") {
|
||||
path := strings.TrimPrefix(registry.Password, "@")
|
||||
out, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
registry.Password = string(out)
|
||||
}
|
||||
|
||||
orgID, err := parseTargetArgs(client, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = client.OrgRegistryUpdate(orgID, registry)
|
||||
return err
|
||||
}
|
44
cli/repo/registry/registry.go
Normal file
44
cli/repo/registry/registry.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
// 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 registry
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
|
||||
)
|
||||
|
||||
// Command exports the registry command set.
|
||||
var Command = &cli.Command{
|
||||
Name: "registry",
|
||||
Usage: "manage registries",
|
||||
Subcommands: []*cli.Command{
|
||||
registryCreateCmd,
|
||||
registryDeleteCmd,
|
||||
registryUpdateCmd,
|
||||
registryInfoCmd,
|
||||
registryListCmd,
|
||||
},
|
||||
}
|
||||
|
||||
func parseTargetArgs(client woodpecker.Client, c *cli.Context) (repoID int64, err error) {
|
||||
repoIDOrFullName := c.String("repository")
|
||||
if repoIDOrFullName == "" {
|
||||
repoIDOrFullName = c.Args().First()
|
||||
}
|
||||
|
||||
return internal.ParseRepo(client, repoIDOrFullName)
|
||||
}
|
|
@ -50,22 +50,15 @@ var registryCreateCmd = &cli.Command{
|
|||
|
||||
func registryCreate(c *cli.Context) error {
|
||||
var (
|
||||
hostname = c.String("hostname")
|
||||
username = c.String("username")
|
||||
password = c.String("password")
|
||||
repoIDOrFullName = c.String("repository")
|
||||
hostname = c.String("hostname")
|
||||
username = c.String("username")
|
||||
password = c.String("password")
|
||||
)
|
||||
if repoIDOrFullName == "" {
|
||||
repoIDOrFullName = c.Args().First()
|
||||
}
|
||||
|
||||
client, err := internal.NewClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
registry := &woodpecker.Registry{
|
||||
Address: hostname,
|
||||
Username: username,
|
||||
|
@ -79,8 +72,12 @@ func registryCreate(c *cli.Context) error {
|
|||
}
|
||||
registry.Password = string(out)
|
||||
}
|
||||
if _, err := client.RegistryCreate(repoID, registry); err != nil {
|
||||
|
||||
repoID, err := parseTargetArgs(client, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
_, err = client.RegistryCreate(repoID, registry)
|
||||
return err
|
||||
}
|
|
@ -42,25 +42,25 @@ var registryInfoCmd = &cli.Command{
|
|||
|
||||
func registryInfo(c *cli.Context) error {
|
||||
var (
|
||||
hostname = c.String("hostname")
|
||||
repoIDOrFullName = c.String("repository")
|
||||
format = c.String("format") + "\n"
|
||||
hostname = c.String("hostname")
|
||||
format = c.String("format") + "\n"
|
||||
)
|
||||
if repoIDOrFullName == "" {
|
||||
repoIDOrFullName = c.Args().First()
|
||||
}
|
||||
|
||||
client, err := internal.NewClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
|
||||
|
||||
repoID, err := parseTargetArgs(client, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
registry, err := client.Registry(repoID, hostname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmpl, err := template.New("_").Parse(format)
|
||||
if err != nil {
|
||||
return err
|
|
@ -36,25 +36,23 @@ var registryListCmd = &cli.Command{
|
|||
}
|
||||
|
||||
func registryList(c *cli.Context) error {
|
||||
var (
|
||||
format = c.String("format") + "\n"
|
||||
repoIDOrFullName = c.String("repository")
|
||||
)
|
||||
if repoIDOrFullName == "" {
|
||||
repoIDOrFullName = c.Args().First()
|
||||
}
|
||||
format := c.String("format") + "\n"
|
||||
|
||||
client, err := internal.NewClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
|
||||
|
||||
repoID, err := parseTargetArgs(client, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
list, err := client.RegistryList(repoID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmpl, err := template.New("_").Parse(format)
|
||||
if err != nil {
|
||||
return err
|
|
@ -37,20 +37,17 @@ var registryDeleteCmd = &cli.Command{
|
|||
}
|
||||
|
||||
func registryDelete(c *cli.Context) error {
|
||||
var (
|
||||
hostname = c.String("hostname")
|
||||
repoIDOrFullName = c.String("repository")
|
||||
)
|
||||
if repoIDOrFullName == "" {
|
||||
repoIDOrFullName = c.Args().First()
|
||||
}
|
||||
hostname := c.String("hostname")
|
||||
|
||||
client, err := internal.NewClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
|
||||
|
||||
repoID, err := parseTargetArgs(client, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return client.RegistryDelete(repoID, hostname)
|
||||
}
|
|
@ -50,22 +50,16 @@ var registryUpdateCmd = &cli.Command{
|
|||
|
||||
func registryUpdate(c *cli.Context) error {
|
||||
var (
|
||||
hostname = c.String("hostname")
|
||||
username = c.String("username")
|
||||
password = c.String("password")
|
||||
repoIDOrFullName = c.String("repository")
|
||||
hostname = c.String("hostname")
|
||||
username = c.String("username")
|
||||
password = c.String("password")
|
||||
)
|
||||
if repoIDOrFullName == "" {
|
||||
repoIDOrFullName = c.Args().First()
|
||||
}
|
||||
|
||||
client, err := internal.NewClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
registry := &woodpecker.Registry{
|
||||
Address: hostname,
|
||||
Username: username,
|
||||
|
@ -79,6 +73,12 @@ func registryUpdate(c *cli.Context) error {
|
|||
}
|
||||
registry.Password = string(out)
|
||||
}
|
||||
|
||||
repoID, err := parseTargetArgs(client, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = client.RegistryUpdate(repoID, registry)
|
||||
return err
|
||||
}
|
|
@ -16,6 +16,8 @@ package repo
|
|||
|
||||
import (
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/cli/repo/registry"
|
||||
)
|
||||
|
||||
// Command exports the repository command.
|
||||
|
@ -31,5 +33,6 @@ var Command = &cli.Command{
|
|||
repoRepairCmd,
|
||||
repoChownCmd,
|
||||
repoSyncCmd,
|
||||
registry.Command,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ package main
|
|||
import (
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"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"
|
||||
|
@ -25,9 +26,10 @@ import (
|
|||
"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/registry"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/cli/repo"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/cli/repo/registry"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/cli/secret"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/cli/setup"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/cli/update"
|
||||
|
@ -47,14 +49,17 @@ func newApp() *cli.App {
|
|||
app.After = common.After
|
||||
app.Suggest = true
|
||||
app.Commands = []*cli.Command{
|
||||
admin.Command,
|
||||
org.Command,
|
||||
repo.Command,
|
||||
pipeline.Command,
|
||||
log.Command,
|
||||
deploy.Command,
|
||||
exec.Command,
|
||||
info.Command,
|
||||
// TODO: Remove in 3.x
|
||||
registry.Command,
|
||||
secret.Command,
|
||||
repo.Command,
|
||||
user.Command,
|
||||
lint.Command,
|
||||
loglevel.Command,
|
||||
|
|
|
@ -1123,6 +1123,233 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/orgs/{org_id}/registries": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Organization registries"
|
||||
],
|
||||
"summary": "List organization registries",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"default": "Bearer \u003cpersonal access token\u003e",
|
||||
"description": "Insert your personal access token",
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "the org's id",
|
||||
"name": "org_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 1,
|
||||
"description": "for response pagination, page offset number",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 50,
|
||||
"description": "for response pagination, max items per page",
|
||||
"name": "perPage",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Registry"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Organization registries"
|
||||
],
|
||||
"summary": "Create an organization registry",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"default": "Bearer \u003cpersonal access token\u003e",
|
||||
"description": "Insert your personal access token",
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "the org's id",
|
||||
"name": "org_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "the new registry",
|
||||
"name": "registryData",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Registry"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Registry"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/orgs/{org_id}/registries/{registry}": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Organization registries"
|
||||
],
|
||||
"summary": "Get a organization registry by address",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"default": "Bearer \u003cpersonal access token\u003e",
|
||||
"description": "Insert your personal access token",
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "the org's id",
|
||||
"name": "org_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "the registry's address",
|
||||
"name": "registry",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Registry"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"produces": [
|
||||
"text/plain"
|
||||
],
|
||||
"tags": [
|
||||
"Organization registries"
|
||||
],
|
||||
"summary": "Delete an organization registry by name",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"default": "Bearer \u003cpersonal access token\u003e",
|
||||
"description": "Insert your personal access token",
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "the org's id",
|
||||
"name": "org_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "the registry's name",
|
||||
"name": "registry",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
}
|
||||
}
|
||||
},
|
||||
"patch": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Organization registries"
|
||||
],
|
||||
"summary": "Update an organization registry by name",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"default": "Bearer \u003cpersonal access token\u003e",
|
||||
"description": "Insert your personal access token",
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "the org's id",
|
||||
"name": "org_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "the registry's name",
|
||||
"name": "registry",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "the update registry data",
|
||||
"name": "registryData",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Registry"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Registry"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/orgs/{org_id}/secrets": {
|
||||
"get": {
|
||||
"produces": [
|
||||
|
@ -1493,6 +1720,198 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/registries": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Registries"
|
||||
],
|
||||
"summary": "List global registries",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"default": "Bearer \u003cpersonal access token\u003e",
|
||||
"description": "Insert your personal access token",
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 1,
|
||||
"description": "for response pagination, page offset number",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 50,
|
||||
"description": "for response pagination, max items per page",
|
||||
"name": "perPage",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Registry"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Registries"
|
||||
],
|
||||
"summary": "Create a global registry",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"default": "Bearer \u003cpersonal access token\u003e",
|
||||
"description": "Insert your personal access token",
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "the registry object data",
|
||||
"name": "registry",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Registry"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Registry"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/registries/{registry}": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Registries"
|
||||
],
|
||||
"summary": "Get a global registry by name",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"default": "Bearer \u003cpersonal access token\u003e",
|
||||
"description": "Insert your personal access token",
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "the registry's name",
|
||||
"name": "registry",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Registry"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"produces": [
|
||||
"text/plain"
|
||||
],
|
||||
"tags": [
|
||||
"Registries"
|
||||
],
|
||||
"summary": "Delete a global registry by name",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"default": "Bearer \u003cpersonal access token\u003e",
|
||||
"description": "Insert your personal access token",
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "the registry's name",
|
||||
"name": "registry",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
}
|
||||
}
|
||||
},
|
||||
"patch": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Registries"
|
||||
],
|
||||
"summary": "Update a global registry by name",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"default": "Bearer \u003cpersonal access token\u003e",
|
||||
"description": "Insert your personal access token",
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "the registry's name",
|
||||
"name": "registry",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "the registry's data",
|
||||
"name": "registryData",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Registry"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Registry"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/repos": {
|
||||
"get": {
|
||||
"description": "Returns a list of all repositories. Requires admin rights.",
|
||||
|
@ -2799,7 +3218,7 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/repos/{repo_id}/registry": {
|
||||
"/repos/{repo_id}/registries": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
|
@ -2895,7 +3314,7 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/repos/{repo_id}/registry/{registry}": {
|
||||
"/repos/{repo_id}/registries/{registry}": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
|
@ -4376,9 +4795,18 @@ const docTemplate = `{
|
|||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"org_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
"readonly": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"repo_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.34.1
|
||||
// protoc-gen-go v1.34.2
|
||||
// protoc v4.25.1
|
||||
// source: woodpecker.proto
|
||||
|
||||
|
@ -1312,7 +1312,7 @@ func file_woodpecker_proto_rawDescGZIP() []byte {
|
|||
}
|
||||
|
||||
var file_woodpecker_proto_msgTypes = make([]protoimpl.MessageInfo, 21)
|
||||
var file_woodpecker_proto_goTypes = []interface{}{
|
||||
var file_woodpecker_proto_goTypes = []any{
|
||||
(*StepState)(nil), // 0: proto.StepState
|
||||
(*WorkflowState)(nil), // 1: proto.WorkflowState
|
||||
(*LogEntry)(nil), // 2: proto.LogEntry
|
||||
|
@ -1380,7 +1380,7 @@ func file_woodpecker_proto_init() {
|
|||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_woodpecker_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_woodpecker_proto_msgTypes[0].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*StepState); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -1392,7 +1392,7 @@ func file_woodpecker_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_woodpecker_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_woodpecker_proto_msgTypes[1].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*WorkflowState); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -1404,7 +1404,7 @@ func file_woodpecker_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_woodpecker_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_woodpecker_proto_msgTypes[2].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*LogEntry); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -1416,7 +1416,7 @@ func file_woodpecker_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_woodpecker_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_woodpecker_proto_msgTypes[3].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*Filter); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -1428,7 +1428,7 @@ func file_woodpecker_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_woodpecker_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_woodpecker_proto_msgTypes[4].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*Workflow); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -1440,7 +1440,7 @@ func file_woodpecker_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_woodpecker_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_woodpecker_proto_msgTypes[5].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*NextRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -1452,7 +1452,7 @@ func file_woodpecker_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_woodpecker_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_woodpecker_proto_msgTypes[6].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*InitRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -1464,7 +1464,7 @@ func file_woodpecker_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_woodpecker_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_woodpecker_proto_msgTypes[7].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*WaitRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -1476,7 +1476,7 @@ func file_woodpecker_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_woodpecker_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_woodpecker_proto_msgTypes[8].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*DoneRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -1488,7 +1488,7 @@ func file_woodpecker_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_woodpecker_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_woodpecker_proto_msgTypes[9].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*ExtendRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -1500,7 +1500,7 @@ func file_woodpecker_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_woodpecker_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_woodpecker_proto_msgTypes[10].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*UpdateRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -1512,7 +1512,7 @@ func file_woodpecker_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_woodpecker_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_woodpecker_proto_msgTypes[11].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*LogRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -1524,7 +1524,7 @@ func file_woodpecker_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_woodpecker_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_woodpecker_proto_msgTypes[12].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*Empty); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -1536,7 +1536,7 @@ func file_woodpecker_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_woodpecker_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_woodpecker_proto_msgTypes[13].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*ReportHealthRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -1548,7 +1548,7 @@ func file_woodpecker_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_woodpecker_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_woodpecker_proto_msgTypes[14].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*RegisterAgentRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -1560,7 +1560,7 @@ func file_woodpecker_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_woodpecker_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_woodpecker_proto_msgTypes[15].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*VersionResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -1572,7 +1572,7 @@ func file_woodpecker_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_woodpecker_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_woodpecker_proto_msgTypes[16].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*NextResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -1584,7 +1584,7 @@ func file_woodpecker_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_woodpecker_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_woodpecker_proto_msgTypes[17].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*RegisterAgentResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -1596,7 +1596,7 @@ func file_woodpecker_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_woodpecker_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_woodpecker_proto_msgTypes[18].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*AuthRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -1608,7 +1608,7 @@ func file_woodpecker_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_woodpecker_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_woodpecker_proto_msgTypes[19].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*AuthResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
|
170
server/api/global_registry.go
Normal file
170
server/api/global_registry.go
Normal file
|
@ -0,0 +1,170 @@
|
|||
// Copyright 2024 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 api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/router/middleware/session"
|
||||
)
|
||||
|
||||
// GetGlobalRegistryList
|
||||
//
|
||||
// @Summary List global registries
|
||||
// @Router /registries [get]
|
||||
// @Produce json
|
||||
// @Success 200 {array} Registry
|
||||
// @Tags Registries
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param page query int false "for response pagination, page offset number" default(1)
|
||||
// @Param perPage query int false "for response pagination, max items per page" default(50)
|
||||
func GetGlobalRegistryList(c *gin.Context) {
|
||||
registryService := server.Config.Services.Manager.RegistryService()
|
||||
list, err := registryService.GlobalRegistryList(session.Pagination(c))
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "Error getting global registry list. %s", err)
|
||||
return
|
||||
}
|
||||
// copy the registry detail to remove the sensitive
|
||||
// password and token fields.
|
||||
for i, registry := range list {
|
||||
list[i] = registry.Copy()
|
||||
}
|
||||
c.JSON(http.StatusOK, list)
|
||||
}
|
||||
|
||||
// GetGlobalRegistry
|
||||
//
|
||||
// @Summary Get a global registry by name
|
||||
// @Router /registries/{registry} [get]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Registry
|
||||
// @Tags Registries
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param registry path string true "the registry's name"
|
||||
func GetGlobalRegistry(c *gin.Context) {
|
||||
addr := c.Param("registry")
|
||||
registryService := server.Config.Services.Manager.RegistryService()
|
||||
registry, err := registryService.GlobalRegistryFind(addr)
|
||||
if err != nil {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, registry.Copy())
|
||||
}
|
||||
|
||||
// PostGlobalRegistry
|
||||
//
|
||||
// @Summary Create a global registry
|
||||
// @Router /registries [post]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Registry
|
||||
// @Tags Registries
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param registry body Registry true "the registry object data"
|
||||
func PostGlobalRegistry(c *gin.Context) {
|
||||
in := new(model.Registry)
|
||||
if err := c.Bind(in); err != nil {
|
||||
c.String(http.StatusBadRequest, "Error parsing global registry. %s", err)
|
||||
return
|
||||
}
|
||||
registry := &model.Registry{
|
||||
Address: in.Address,
|
||||
Username: in.Username,
|
||||
Password: in.Password,
|
||||
}
|
||||
if err := registry.Validate(); err != nil {
|
||||
c.String(http.StatusBadRequest, "Error inserting global registry. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
registryService := server.Config.Services.Manager.RegistryService()
|
||||
if err := registryService.GlobalRegistryCreate(registry); err != nil {
|
||||
c.String(http.StatusInternalServerError, "Error inserting global registry %q. %s", in.Address, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, registry.Copy())
|
||||
}
|
||||
|
||||
// PatchGlobalRegistry
|
||||
//
|
||||
// @Summary Update a global registry by name
|
||||
// @Router /registries/{registry} [patch]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Registry
|
||||
// @Tags Registries
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param registry path string true "the registry's name"
|
||||
// @Param registryData body Registry true "the registry's data"
|
||||
func PatchGlobalRegistry(c *gin.Context) {
|
||||
addr := c.Param("registry")
|
||||
|
||||
in := new(model.Registry)
|
||||
err := c.Bind(in)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Error parsing registry. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
registryService := server.Config.Services.Manager.RegistryService()
|
||||
registry, err := registryService.GlobalRegistryFind(addr)
|
||||
if err != nil {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
}
|
||||
if in.Address != "" {
|
||||
registry.Address = in.Address
|
||||
}
|
||||
if in.Username != "" {
|
||||
registry.Username = in.Username
|
||||
}
|
||||
if in.Password != "" {
|
||||
registry.Password = in.Password
|
||||
}
|
||||
|
||||
if err := registry.Validate(); err != nil {
|
||||
c.String(http.StatusBadRequest, "Error updating global registry. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := registryService.GlobalRegistryUpdate(registry); err != nil {
|
||||
c.String(http.StatusInternalServerError, "Error updating global registry %q. %s", in.Address, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, registry.Copy())
|
||||
}
|
||||
|
||||
// DeleteGlobalRegistry
|
||||
//
|
||||
// @Summary Delete a global registry by name
|
||||
// @Router /registries/{registry} [delete]
|
||||
// @Produce plain
|
||||
// @Success 204
|
||||
// @Tags Registries
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param registry path string true "the registry's name"
|
||||
func DeleteGlobalRegistry(c *gin.Context) {
|
||||
addr := c.Param("registry")
|
||||
registryService := server.Config.Services.Manager.RegistryService()
|
||||
if err := registryService.GlobalRegistryDelete(addr); err != nil {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
}
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
207
server/api/org_registry.go
Normal file
207
server/api/org_registry.go
Normal file
|
@ -0,0 +1,207 @@
|
|||
// Copyright 2024 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 api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/router/middleware/session"
|
||||
)
|
||||
|
||||
// GetOrgRegistry
|
||||
//
|
||||
// @Summary Get a organization registry by address
|
||||
// @Router /orgs/{org_id}/registries/{registry} [get]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Registry
|
||||
// @Tags Organization registries
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param org_id path string true "the org's id"
|
||||
// @Param registry path string true "the registry's address"
|
||||
func GetOrgRegistry(c *gin.Context) {
|
||||
addr := c.Param("registry")
|
||||
|
||||
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Error parsing org id. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
registryService := server.Config.Services.Manager.RegistryService()
|
||||
registry, err := registryService.OrgRegistryFind(orgID, addr)
|
||||
if err != nil {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, registry.Copy())
|
||||
}
|
||||
|
||||
// GetOrgRegistryList
|
||||
//
|
||||
// @Summary List organization registries
|
||||
// @Router /orgs/{org_id}/registries [get]
|
||||
// @Produce json
|
||||
// @Success 200 {array} Registry
|
||||
// @Tags Organization registries
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param org_id path string true "the org's id"
|
||||
// @Param page query int false "for response pagination, page offset number" default(1)
|
||||
// @Param perPage query int false "for response pagination, max items per page" default(50)
|
||||
func GetOrgRegistryList(c *gin.Context) {
|
||||
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Error parsing org id. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
registryService := server.Config.Services.Manager.RegistryService()
|
||||
list, err := registryService.OrgRegistryList(orgID, session.Pagination(c))
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "Error getting registry list for %q. %s", orgID, err)
|
||||
return
|
||||
}
|
||||
// copy the registry detail to remove the sensitive
|
||||
// password and token fields.
|
||||
for i, registry := range list {
|
||||
list[i] = registry.Copy()
|
||||
}
|
||||
c.JSON(http.StatusOK, list)
|
||||
}
|
||||
|
||||
// PostOrgRegistry
|
||||
//
|
||||
// @Summary Create an organization registry
|
||||
// @Router /orgs/{org_id}/registries [post]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Registry
|
||||
// @Tags Organization registries
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param org_id path string true "the org's id"
|
||||
// @Param registryData body Registry true "the new registry"
|
||||
func PostOrgRegistry(c *gin.Context) {
|
||||
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Error parsing org id. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
in := new(model.Registry)
|
||||
if err := c.Bind(in); err != nil {
|
||||
c.String(http.StatusBadRequest, "Error parsing org %q registry. %s", orgID, err)
|
||||
return
|
||||
}
|
||||
registry := &model.Registry{
|
||||
OrgID: orgID,
|
||||
Address: in.Address,
|
||||
Username: in.Username,
|
||||
Password: in.Password,
|
||||
}
|
||||
if err := registry.Validate(); err != nil {
|
||||
c.String(http.StatusUnprocessableEntity, "Error inserting org %q registry. %s", orgID, err)
|
||||
return
|
||||
}
|
||||
|
||||
registryService := server.Config.Services.Manager.RegistryService()
|
||||
if err := registryService.OrgRegistryCreate(orgID, registry); err != nil {
|
||||
c.String(http.StatusInternalServerError, "Error inserting org %q registry %q. %s", orgID, in.Address, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, registry.Copy())
|
||||
}
|
||||
|
||||
// PatchOrgRegistry
|
||||
//
|
||||
// @Summary Update an organization registry by name
|
||||
// @Router /orgs/{org_id}/registries/{registry} [patch]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Registry
|
||||
// @Tags Organization registries
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param org_id path string true "the org's id"
|
||||
// @Param registry path string true "the registry's name"
|
||||
// @Param registryData body Registry true "the update registry data"
|
||||
func PatchOrgRegistry(c *gin.Context) {
|
||||
addr := c.Param("registry")
|
||||
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Error parsing org id. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
in := new(model.Registry)
|
||||
err = c.Bind(in)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Error parsing registry. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
registryService := server.Config.Services.Manager.RegistryService()
|
||||
registry, err := registryService.OrgRegistryFind(orgID, addr)
|
||||
if err != nil {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
}
|
||||
if in.Address != "" {
|
||||
registry.Address = in.Address
|
||||
}
|
||||
if in.Username != "" {
|
||||
registry.Username = in.Username
|
||||
}
|
||||
if in.Password != "" {
|
||||
registry.Password = in.Password
|
||||
}
|
||||
|
||||
if err := registry.Validate(); err != nil {
|
||||
c.String(http.StatusUnprocessableEntity, "Error updating org %q registry. %s", orgID, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := registryService.OrgRegistryUpdate(orgID, registry); err != nil {
|
||||
c.String(http.StatusInternalServerError, "Error updating org %q registry %q. %s", orgID, in.Address, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, registry.Copy())
|
||||
}
|
||||
|
||||
// DeleteOrgRegistry
|
||||
//
|
||||
// @Summary Delete an organization registry by name
|
||||
// @Router /orgs/{org_id}/registries/{registry} [delete]
|
||||
// @Produce plain
|
||||
// @Success 204
|
||||
// @Tags Organization registries
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param org_id path string true "the org's id"
|
||||
// @Param registry path string true "the registry's name"
|
||||
func DeleteOrgRegistry(c *gin.Context) {
|
||||
addr := c.Param("registry")
|
||||
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Error parsing org id. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
registryService := server.Config.Services.Manager.RegistryService()
|
||||
if err := registryService.OrgRegistryDelete(orgID, addr); err != nil {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
}
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
|
@ -27,7 +27,7 @@ import (
|
|||
// GetRegistry
|
||||
//
|
||||
// @Summary Get a registry by name
|
||||
// @Router /repos/{repo_id}/registry/{registry} [get]
|
||||
// @Router /repos/{repo_id}/registries/{registry} [get]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Registry
|
||||
// @Tags Repository registries
|
||||
|
@ -36,10 +36,10 @@ import (
|
|||
// @Param registry path string true "the registry name"
|
||||
func GetRegistry(c *gin.Context) {
|
||||
repo := session.Repo(c)
|
||||
name := c.Param("registry")
|
||||
addr := c.Param("registry")
|
||||
|
||||
registryService := server.Config.Services.Manager.RegistryServiceFromRepo(repo)
|
||||
registry, err := registryService.RegistryFind(repo, name)
|
||||
registry, err := registryService.RegistryFind(repo, addr)
|
||||
if err != nil {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
|
@ -50,7 +50,7 @@ func GetRegistry(c *gin.Context) {
|
|||
// PostRegistry
|
||||
//
|
||||
// @Summary Create a registry
|
||||
// @Router /repos/{repo_id}/registry [post]
|
||||
// @Router /repos/{repo_id}/registries [post]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Registry
|
||||
// @Tags Repository registries
|
||||
|
@ -87,7 +87,7 @@ func PostRegistry(c *gin.Context) {
|
|||
// PatchRegistry
|
||||
//
|
||||
// @Summary Update a registry by name
|
||||
// @Router /repos/{repo_id}/registry/{registry} [patch]
|
||||
// @Router /repos/{repo_id}/registries/{registry} [patch]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Registry
|
||||
// @Tags Repository registries
|
||||
|
@ -96,10 +96,8 @@ func PostRegistry(c *gin.Context) {
|
|||
// @Param registry path string true "the registry name"
|
||||
// @Param registryData body Registry true "the attributes for the registry"
|
||||
func PatchRegistry(c *gin.Context) {
|
||||
var (
|
||||
repo = session.Repo(c)
|
||||
name = c.Param("registry")
|
||||
)
|
||||
repo := session.Repo(c)
|
||||
addr := c.Param("registry")
|
||||
|
||||
in := new(model.Registry)
|
||||
err := c.Bind(in)
|
||||
|
@ -109,7 +107,7 @@ func PatchRegistry(c *gin.Context) {
|
|||
}
|
||||
|
||||
registryService := server.Config.Services.Manager.RegistryServiceFromRepo(repo)
|
||||
registry, err := registryService.RegistryFind(repo, name)
|
||||
registry, err := registryService.RegistryFind(repo, addr)
|
||||
if err != nil {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
|
@ -135,7 +133,7 @@ func PatchRegistry(c *gin.Context) {
|
|||
// GetRegistryList
|
||||
//
|
||||
// @Summary List registries
|
||||
// @Router /repos/{repo_id}/registry [get]
|
||||
// @Router /repos/{repo_id}/registries [get]
|
||||
// @Produce json
|
||||
// @Success 200 {array} Registry
|
||||
// @Tags Repository registries
|
||||
|
@ -162,7 +160,7 @@ func GetRegistryList(c *gin.Context) {
|
|||
// DeleteRegistry
|
||||
//
|
||||
// @Summary Delete a registry by name
|
||||
// @Router /repos/{repo_id}/registry/{registry} [delete]
|
||||
// @Router /repos/{repo_id}/registries/{registry} [delete]
|
||||
// @Produce plain
|
||||
// @Success 204
|
||||
// @Tags Repository registries
|
||||
|
@ -171,10 +169,10 @@ func GetRegistryList(c *gin.Context) {
|
|||
// @Param registry path string true "the registry name"
|
||||
func DeleteRegistry(c *gin.Context) {
|
||||
repo := session.Repo(c)
|
||||
name := c.Param("registry")
|
||||
addr := c.Param("registry")
|
||||
|
||||
registryService := server.Config.Services.Manager.RegistryServiceFromRepo(repo)
|
||||
err := registryService.RegistryDelete(repo, name)
|
||||
err := registryService.RegistryDelete(repo, addr)
|
||||
if err != nil {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
|
|
|
@ -29,16 +29,33 @@ var (
|
|||
// Registry represents a docker registry with credentials.
|
||||
type Registry struct {
|
||||
ID int64 `json:"id" xorm:"pk autoincr 'id'"`
|
||||
RepoID int64 `json:"-" xorm:"UNIQUE(s) INDEX 'repo_id'"`
|
||||
Address string `json:"address" xorm:"UNIQUE(s) INDEX 'address'"`
|
||||
OrgID int64 `json:"org_id" xorm:"NOT NULL DEFAULT 0 UNIQUE(s) INDEX 'org_id'"`
|
||||
RepoID int64 `json:"repo_id" xorm:"NOT NULL DEFAULT 0 UNIQUE(s) INDEX 'repo_id'"`
|
||||
Address string `json:"address" xorm:"NOT NULL UNIQUE(s) INDEX 'address'"`
|
||||
Username string `json:"username" xorm:"varchar(2000) 'username'"`
|
||||
Password string `json:"password" xorm:"TEXT 'password'"`
|
||||
ReadOnly bool `json:"readonly" xorm:"-"`
|
||||
} // @name Registry
|
||||
|
||||
func (r Registry) TableName() string {
|
||||
return "registries"
|
||||
}
|
||||
|
||||
// Global registry.
|
||||
func (r Registry) IsGlobal() bool {
|
||||
return r.RepoID == 0 && r.OrgID == 0
|
||||
}
|
||||
|
||||
// Organization registry.
|
||||
func (r Registry) IsOrganization() bool {
|
||||
return r.RepoID == 0 && r.OrgID != 0
|
||||
}
|
||||
|
||||
// Repository registry.
|
||||
func (r Registry) IsRepository() bool {
|
||||
return r.RepoID != 0 && r.OrgID == 0
|
||||
}
|
||||
|
||||
// Validate validates the registry information.
|
||||
func (r *Registry) Validate() error {
|
||||
switch {
|
||||
|
@ -58,8 +75,10 @@ func (r *Registry) Validate() error {
|
|||
func (r *Registry) Copy() *Registry {
|
||||
return &Registry{
|
||||
ID: r.ID,
|
||||
OrgID: r.OrgID,
|
||||
RepoID: r.RepoID,
|
||||
Address: r.Address,
|
||||
Username: r.Username,
|
||||
ReadOnly: r.ReadOnly,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,13 +44,13 @@ func parsePipeline(forge forge.Forge, store store.Store, currentPipeline *model.
|
|||
}
|
||||
|
||||
secretService := server.Config.Services.Manager.SecretServiceFromRepo(repo)
|
||||
secs, err := secretService.SecretListPipeline(repo, currentPipeline, &model.ListOptions{All: true})
|
||||
secs, err := secretService.SecretListPipeline(repo, currentPipeline)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("error getting secrets for %s#%d", repo.FullName, currentPipeline.Number)
|
||||
}
|
||||
|
||||
registryService := server.Config.Services.Manager.RegistryServiceFromRepo(repo)
|
||||
regs, err := registryService.RegistryList(repo, &model.ListOptions{All: true})
|
||||
regs, err := registryService.RegistryListPipeline(repo, currentPipeline)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("error getting registry credentials for %s#%d", repo.FullName, currentPipeline.Number)
|
||||
}
|
||||
|
|
|
@ -61,11 +61,18 @@ func apiRoutes(e *gin.RouterGroup) {
|
|||
org.Use(session.MustOrgMember(true))
|
||||
org.DELETE("", session.MustAdmin(), api.DeleteOrg)
|
||||
org.GET("", api.GetOrg)
|
||||
|
||||
org.GET("/secrets", api.GetOrgSecretList)
|
||||
org.POST("/secrets", api.PostOrgSecret)
|
||||
org.GET("/secrets/:secret", api.GetOrgSecret)
|
||||
org.PATCH("/secrets/:secret", api.PatchOrgSecret)
|
||||
org.DELETE("/secrets/:secret", api.DeleteOrgSecret)
|
||||
|
||||
org.GET("/registries", api.GetOrgRegistryList)
|
||||
org.POST("/registries", api.PostOrgRegistry)
|
||||
org.GET("/registries/:registry", api.GetOrgRegistry)
|
||||
org.PATCH("/registries/:registry", api.PatchOrgRegistry)
|
||||
org.DELETE("/registries/:registry", api.DeleteOrgRegistry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -118,6 +125,13 @@ func apiRoutes(e *gin.RouterGroup) {
|
|||
repo.DELETE("/secrets/:secret", session.MustPush, api.DeleteSecret)
|
||||
|
||||
// requires push permissions
|
||||
repo.GET("/registries", session.MustPush, api.GetRegistryList)
|
||||
repo.POST("/registries", session.MustPush, api.PostRegistry)
|
||||
repo.GET("/registries/:registry", session.MustPush, api.GetRegistry)
|
||||
repo.PATCH("/registries/:registry", session.MustPush, api.PatchRegistry)
|
||||
repo.DELETE("/registries/:registry", session.MustPush, api.DeleteRegistry)
|
||||
|
||||
// TODO: remove with 3.x
|
||||
repo.GET("/registry", session.MustPush, api.GetRegistryList)
|
||||
repo.POST("/registry", session.MustPush, api.PostRegistry)
|
||||
repo.GET("/registry/:registry", session.MustPush, api.GetRegistry)
|
||||
|
@ -184,6 +198,21 @@ func apiRoutes(e *gin.RouterGroup) {
|
|||
secrets.DELETE("/:secret", api.DeleteGlobalSecret)
|
||||
}
|
||||
|
||||
// global registries can be read without actual values by any user
|
||||
readGlobalRegistries := apiBase.Group("/registries")
|
||||
{
|
||||
readGlobalRegistries.Use(session.MustUser())
|
||||
readGlobalRegistries.GET("", api.GetGlobalRegistryList)
|
||||
readGlobalRegistries.GET("/:registry", api.GetGlobalRegistry)
|
||||
}
|
||||
registries := apiBase.Group("/registries")
|
||||
{
|
||||
registries.Use(session.MustAdmin())
|
||||
registries.POST("", api.PostGlobalRegistry)
|
||||
registries.PATCH("/:registry", api.PatchGlobalRegistry)
|
||||
registries.DELETE("/:registry", api.DeleteGlobalRegistry)
|
||||
}
|
||||
|
||||
logLevel := apiBase.Group("/log-level")
|
||||
{
|
||||
logLevel.Use(session.MustAdmin())
|
||||
|
|
|
@ -15,7 +15,10 @@
|
|||
package registry
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/store/types"
|
||||
)
|
||||
|
||||
type combined struct {
|
||||
|
@ -31,29 +34,44 @@ func NewCombined(dbRegistry Service, registries ...ReadOnlyService) Service {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *combined) RegistryFind(repo *model.Repo, name string) (*model.Registry, error) {
|
||||
for _, registry := range c.registries {
|
||||
res, err := registry.RegistryFind(repo, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res != nil {
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
func (c *combined) RegistryFind(repo *model.Repo, addr string) (*model.Registry, error) {
|
||||
return c.dbRegistry.RegistryFind(repo, addr)
|
||||
}
|
||||
|
||||
func (c *combined) RegistryList(repo *model.Repo, p *model.ListOptions) ([]*model.Registry, error) {
|
||||
var registries []*model.Registry
|
||||
return c.dbRegistry.RegistryList(repo, p)
|
||||
}
|
||||
|
||||
func (c *combined) RegistryListPipeline(repo *model.Repo, pipeline *model.Pipeline) ([]*model.Registry, error) {
|
||||
dbRegistries, err := c.dbRegistry.RegistryListPipeline(repo, pipeline)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
registries := make([]*model.Registry, 0, len(dbRegistries))
|
||||
exists := make(map[string]struct{}, len(dbRegistries))
|
||||
|
||||
// Assign database stored registries to the map to avoid duplicates
|
||||
// from the combined registries so to prioritize ones in database.
|
||||
for _, reg := range dbRegistries {
|
||||
exists[reg.Address] = struct{}{}
|
||||
}
|
||||
|
||||
for _, registry := range c.registries {
|
||||
list, err := registry.RegistryList(repo, &model.ListOptions{All: true})
|
||||
list, err := registry.GlobalRegistryList(&model.ListOptions{All: true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
registries = append(registries, list...)
|
||||
for _, reg := range list {
|
||||
if _, ok := exists[reg.Address]; ok {
|
||||
continue
|
||||
}
|
||||
exists[reg.Address] = struct{}{}
|
||||
registries = append(registries, reg)
|
||||
}
|
||||
}
|
||||
return model.ApplyPagination(p, registries), nil
|
||||
|
||||
return append(registries, dbRegistries...), nil
|
||||
}
|
||||
|
||||
func (c *combined) RegistryCreate(repo *model.Repo, registry *model.Registry) error {
|
||||
|
@ -64,6 +82,86 @@ func (c *combined) RegistryUpdate(repo *model.Repo, registry *model.Registry) er
|
|||
return c.dbRegistry.RegistryUpdate(repo, registry)
|
||||
}
|
||||
|
||||
func (c *combined) RegistryDelete(repo *model.Repo, name string) error {
|
||||
return c.dbRegistry.RegistryDelete(repo, name)
|
||||
func (c *combined) RegistryDelete(repo *model.Repo, addr string) error {
|
||||
return c.dbRegistry.RegistryDelete(repo, addr)
|
||||
}
|
||||
|
||||
func (c *combined) OrgRegistryFind(owner int64, addr string) (*model.Registry, error) {
|
||||
return c.dbRegistry.OrgRegistryFind(owner, addr)
|
||||
}
|
||||
|
||||
func (c *combined) OrgRegistryList(owner int64, p *model.ListOptions) ([]*model.Registry, error) {
|
||||
return c.dbRegistry.OrgRegistryList(owner, p)
|
||||
}
|
||||
|
||||
func (c *combined) OrgRegistryCreate(owner int64, registry *model.Registry) error {
|
||||
return c.dbRegistry.OrgRegistryCreate(owner, registry)
|
||||
}
|
||||
|
||||
func (c *combined) OrgRegistryUpdate(owner int64, registry *model.Registry) error {
|
||||
return c.dbRegistry.OrgRegistryUpdate(owner, registry)
|
||||
}
|
||||
|
||||
func (c *combined) OrgRegistryDelete(owner int64, addr string) error {
|
||||
return c.dbRegistry.OrgRegistryDelete(owner, addr)
|
||||
}
|
||||
|
||||
func (c *combined) GlobalRegistryFind(addr string) (*model.Registry, error) {
|
||||
registry, err := c.dbRegistry.GlobalRegistryFind(addr)
|
||||
if err != nil && !errors.Is(err, types.RecordNotExist) {
|
||||
return nil, err
|
||||
}
|
||||
if registry != nil {
|
||||
return registry, nil
|
||||
}
|
||||
for _, reg := range c.registries {
|
||||
if registry, err := reg.GlobalRegistryFind(addr); err == nil {
|
||||
return registry, nil
|
||||
}
|
||||
}
|
||||
return nil, types.RecordNotExist
|
||||
}
|
||||
|
||||
func (c *combined) GlobalRegistryList(p *model.ListOptions) ([]*model.Registry, error) {
|
||||
dbRegistries, err := c.dbRegistry.GlobalRegistryList(&model.ListOptions{All: true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
registries := make([]*model.Registry, 0, len(dbRegistries))
|
||||
exists := make(map[string]struct{}, len(dbRegistries))
|
||||
|
||||
// Assign database stored registries to the map to avoid duplicates
|
||||
// from the combined registries so to prioritize ones in database.
|
||||
for _, reg := range dbRegistries {
|
||||
exists[reg.Address] = struct{}{}
|
||||
}
|
||||
|
||||
for _, registry := range c.registries {
|
||||
list, err := registry.GlobalRegistryList(&model.ListOptions{All: true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, reg := range list {
|
||||
if _, ok := exists[reg.Address]; ok {
|
||||
continue
|
||||
}
|
||||
exists[reg.Address] = struct{}{}
|
||||
registries = append(registries, reg)
|
||||
}
|
||||
}
|
||||
|
||||
return model.ApplyPagination(p, append(registries, dbRegistries...)), nil
|
||||
}
|
||||
|
||||
func (c *combined) GlobalRegistryCreate(registry *model.Registry) error {
|
||||
return c.dbRegistry.GlobalRegistryCreate(registry)
|
||||
}
|
||||
|
||||
func (c *combined) GlobalRegistryUpdate(registry *model.Registry) error {
|
||||
return c.dbRegistry.GlobalRegistryUpdate(registry)
|
||||
}
|
||||
|
||||
func (c *combined) GlobalRegistryDelete(addr string) error {
|
||||
return c.dbRegistry.GlobalRegistryDelete(addr)
|
||||
}
|
||||
|
|
|
@ -28,12 +28,45 @@ func NewDB(store store.Store) Service {
|
|||
return &db{store}
|
||||
}
|
||||
|
||||
func (d *db) RegistryFind(repo *model.Repo, name string) (*model.Registry, error) {
|
||||
return d.store.RegistryFind(repo, name)
|
||||
func (d *db) RegistryFind(repo *model.Repo, addr string) (*model.Registry, error) {
|
||||
return d.store.RegistryFind(repo, addr)
|
||||
}
|
||||
|
||||
func (d *db) RegistryList(repo *model.Repo, p *model.ListOptions) ([]*model.Registry, error) {
|
||||
return d.store.RegistryList(repo, p)
|
||||
return d.store.RegistryList(repo, false, p)
|
||||
}
|
||||
|
||||
func (d *db) RegistryListPipeline(repo *model.Repo, _ *model.Pipeline) ([]*model.Registry, error) {
|
||||
r, err := d.store.RegistryList(repo, true, &model.ListOptions{All: true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Return only registries with unique address
|
||||
// Priority order in case of duplicate addresses are repository, user/organization, global
|
||||
registries := make([]*model.Registry, 0, len(r))
|
||||
uniq := make(map[string]struct{})
|
||||
for _, condition := range []struct {
|
||||
IsRepository bool
|
||||
IsOrganization bool
|
||||
IsGlobal bool
|
||||
}{
|
||||
{IsRepository: true},
|
||||
{IsOrganization: true},
|
||||
{IsGlobal: true},
|
||||
} {
|
||||
for _, registry := range r {
|
||||
if registry.IsRepository() != condition.IsRepository || registry.IsOrganization() != condition.IsOrganization || registry.IsGlobal() != condition.IsGlobal {
|
||||
continue
|
||||
}
|
||||
if _, ok := uniq[registry.Address]; ok {
|
||||
continue
|
||||
}
|
||||
uniq[registry.Address] = struct{}{}
|
||||
registries = append(registries, registry)
|
||||
}
|
||||
}
|
||||
return registries, nil
|
||||
}
|
||||
|
||||
func (d *db) RegistryCreate(_ *model.Repo, in *model.Registry) error {
|
||||
|
@ -45,5 +78,57 @@ func (d *db) RegistryUpdate(_ *model.Repo, in *model.Registry) error {
|
|||
}
|
||||
|
||||
func (d *db) RegistryDelete(repo *model.Repo, addr string) error {
|
||||
return d.store.RegistryDelete(repo, addr)
|
||||
registry, err := d.store.RegistryFind(repo, addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.store.RegistryDelete(registry)
|
||||
}
|
||||
|
||||
func (d *db) OrgRegistryFind(owner int64, name string) (*model.Registry, error) {
|
||||
return d.store.OrgRegistryFind(owner, name)
|
||||
}
|
||||
|
||||
func (d *db) OrgRegistryList(owner int64, p *model.ListOptions) ([]*model.Registry, error) {
|
||||
return d.store.OrgRegistryList(owner, p)
|
||||
}
|
||||
|
||||
func (d *db) OrgRegistryCreate(_ int64, in *model.Registry) error {
|
||||
return d.store.RegistryCreate(in)
|
||||
}
|
||||
|
||||
func (d *db) OrgRegistryUpdate(_ int64, in *model.Registry) error {
|
||||
return d.store.RegistryUpdate(in)
|
||||
}
|
||||
|
||||
func (d *db) OrgRegistryDelete(owner int64, addr string) error {
|
||||
registry, err := d.store.OrgRegistryFind(owner, addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.store.RegistryDelete(registry)
|
||||
}
|
||||
|
||||
func (d *db) GlobalRegistryFind(addr string) (*model.Registry, error) {
|
||||
return d.store.GlobalRegistryFind(addr)
|
||||
}
|
||||
|
||||
func (d *db) GlobalRegistryList(p *model.ListOptions) ([]*model.Registry, error) {
|
||||
return d.store.GlobalRegistryList(p)
|
||||
}
|
||||
|
||||
func (d *db) GlobalRegistryCreate(in *model.Registry) error {
|
||||
return d.store.RegistryCreate(in)
|
||||
}
|
||||
|
||||
func (d *db) GlobalRegistryUpdate(in *model.Registry) error {
|
||||
return d.store.RegistryUpdate(in)
|
||||
}
|
||||
|
||||
func (d *db) GlobalRegistryDelete(addr string) error {
|
||||
registry, err := d.store.GlobalRegistryFind(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.store.RegistryDelete(registry)
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
"github.com/docker/cli/cli/config/types"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
model_types "go.woodpecker-ci.org/woodpecker/v2/server/store/types"
|
||||
)
|
||||
|
||||
type filesystem struct {
|
||||
|
@ -79,17 +80,29 @@ func parseDockerConfig(path string) ([]*model.Registry, error) {
|
|||
Address: key,
|
||||
Username: auth.Username,
|
||||
Password: auth.Password,
|
||||
ReadOnly: true,
|
||||
})
|
||||
}
|
||||
|
||||
return registries, nil
|
||||
}
|
||||
|
||||
func (f *filesystem) RegistryFind(*model.Repo, string) (*model.Registry, error) {
|
||||
return nil, nil
|
||||
func (f *filesystem) GlobalRegistryFind(addr string) (*model.Registry, error) {
|
||||
registries, err := f.GlobalRegistryList(&model.ListOptions{All: true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, reg := range registries {
|
||||
if reg.Address == addr {
|
||||
return reg, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, model_types.RecordNotExist
|
||||
}
|
||||
|
||||
func (f *filesystem) RegistryList(_ *model.Repo, p *model.ListOptions) ([]*model.Registry, error) {
|
||||
func (f *filesystem) GlobalRegistryList(p *model.ListOptions) ([]*model.Registry, error) {
|
||||
regs, err := parseDockerConfig(f.path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -18,15 +18,29 @@ import "go.woodpecker-ci.org/woodpecker/v2/server/model"
|
|||
|
||||
// Service defines a service for managing registries.
|
||||
type Service interface {
|
||||
RegistryListPipeline(*model.Repo, *model.Pipeline) ([]*model.Registry, error)
|
||||
// Repository registries
|
||||
RegistryFind(*model.Repo, string) (*model.Registry, error)
|
||||
RegistryList(*model.Repo, *model.ListOptions) ([]*model.Registry, error)
|
||||
RegistryCreate(*model.Repo, *model.Registry) error
|
||||
RegistryUpdate(*model.Repo, *model.Registry) error
|
||||
RegistryDelete(*model.Repo, string) error
|
||||
// Organization registries
|
||||
OrgRegistryFind(int64, string) (*model.Registry, error)
|
||||
OrgRegistryList(int64, *model.ListOptions) ([]*model.Registry, error)
|
||||
OrgRegistryCreate(int64, *model.Registry) error
|
||||
OrgRegistryUpdate(int64, *model.Registry) error
|
||||
OrgRegistryDelete(int64, string) error
|
||||
// Global registries
|
||||
GlobalRegistryFind(string) (*model.Registry, error)
|
||||
GlobalRegistryList(*model.ListOptions) ([]*model.Registry, error)
|
||||
GlobalRegistryCreate(*model.Registry) error
|
||||
GlobalRegistryUpdate(*model.Registry) error
|
||||
GlobalRegistryDelete(string) error
|
||||
}
|
||||
|
||||
// ReadOnlyService defines a service for managing registries.
|
||||
type ReadOnlyService interface {
|
||||
RegistryFind(*model.Repo, string) (*model.Registry, error)
|
||||
RegistryList(*model.Repo, *model.ListOptions) ([]*model.Registry, error)
|
||||
GlobalRegistryFind(string) (*model.Registry, error)
|
||||
GlobalRegistryList(*model.ListOptions) ([]*model.Registry, error)
|
||||
}
|
||||
|
|
|
@ -36,8 +36,8 @@ func (d *db) SecretList(repo *model.Repo, p *model.ListOptions) ([]*model.Secret
|
|||
return d.store.SecretList(repo, false, p)
|
||||
}
|
||||
|
||||
func (d *db) SecretListPipeline(repo *model.Repo, _ *model.Pipeline, p *model.ListOptions) ([]*model.Secret, error) {
|
||||
s, err := d.store.SecretList(repo, true, p)
|
||||
func (d *db) SecretListPipeline(repo *model.Repo, _ *model.Pipeline) ([]*model.Secret, error) {
|
||||
s, err := d.store.SecretList(repo, true, &model.ListOptions{All: true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ func TestSecretListPipeline(t *testing.T) {
|
|||
repoSecret,
|
||||
}, nil)
|
||||
|
||||
s, err := secret.NewDB(mockStore).SecretListPipeline(&model.Repo{}, &model.Pipeline{}, &model.ListOptions{})
|
||||
s, err := secret.NewDB(mockStore).SecretListPipeline(&model.Repo{}, &model.Pipeline{})
|
||||
g.Assert(err).IsNil()
|
||||
|
||||
g.Assert(len(s)).Equal(1)
|
||||
|
@ -77,7 +77,7 @@ func TestSecretListPipeline(t *testing.T) {
|
|||
orgSecret,
|
||||
}, nil)
|
||||
|
||||
s, err := secret.NewDB(mockStore).SecretListPipeline(&model.Repo{}, &model.Pipeline{}, &model.ListOptions{})
|
||||
s, err := secret.NewDB(mockStore).SecretListPipeline(&model.Repo{}, &model.Pipeline{})
|
||||
g.Assert(err).IsNil()
|
||||
|
||||
g.Assert(len(s)).Equal(1)
|
||||
|
@ -89,7 +89,7 @@ func TestSecretListPipeline(t *testing.T) {
|
|||
globalSecret,
|
||||
}, nil)
|
||||
|
||||
s, err := secret.NewDB(mockStore).SecretListPipeline(&model.Repo{}, &model.Pipeline{}, &model.ListOptions{})
|
||||
s, err := secret.NewDB(mockStore).SecretListPipeline(&model.Repo{}, &model.Pipeline{})
|
||||
g.Assert(err).IsNil()
|
||||
|
||||
g.Assert(len(s)).Equal(1)
|
||||
|
|
|
@ -18,7 +18,7 @@ import "go.woodpecker-ci.org/woodpecker/v2/server/model"
|
|||
|
||||
// Service defines a service for managing secrets.
|
||||
type Service interface {
|
||||
SecretListPipeline(*model.Repo, *model.Pipeline, *model.ListOptions) ([]*model.Secret, error)
|
||||
SecretListPipeline(*model.Repo, *model.Pipeline) ([]*model.Secret, error)
|
||||
// Repository secrets
|
||||
SecretFind(*model.Repo, string) (*model.Secret, error)
|
||||
SecretList(*model.Repo, *model.ListOptions) ([]*model.Secret, error)
|
||||
|
|
33
server/store/datastore/migration/032_registries_add_user.go
Normal file
33
server/store/datastore/migration/032_registries_add_user.go
Normal file
|
@ -0,0 +1,33 @@
|
|||
// Copyright 2024 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 migration
|
||||
|
||||
import (
|
||||
"src.techknowlogick.com/xormigrate"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
var alterTableRegistriesFixRequiredFields = xormigrate.Migration{
|
||||
ID: "alter-table-registries-fix-required-fields",
|
||||
MigrateSession: func(sess *xorm.Session) error {
|
||||
if err := alterColumnDefault(sess, "registries", "repo_id", "0"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := alterColumnNull(sess, "registries", "repo_id", false); err != nil {
|
||||
return err
|
||||
}
|
||||
return alterColumnNull(sess, "registries", "address", false)
|
||||
},
|
||||
}
|
|
@ -195,6 +195,7 @@ func alterColumnDefault(sess *xorm.Session, table, column, defValue string) erro
|
|||
}
|
||||
}
|
||||
|
||||
//nolint:unparam
|
||||
func alterColumnNull(sess *xorm.Session, table, column string, null bool) error {
|
||||
val := "NULL"
|
||||
if !null {
|
||||
|
|
|
@ -61,6 +61,7 @@ var migrationTasks = []*xormigrate.Migration{
|
|||
&cleanRegistryPipeline,
|
||||
&setForgeID,
|
||||
&unifyColumnsTables,
|
||||
&alterTableRegistriesFixRequiredFields,
|
||||
}
|
||||
|
||||
var allBeans = []any{
|
||||
|
|
|
@ -20,6 +20,8 @@ import (
|
|||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
)
|
||||
|
||||
const orderRegistriesBy = "id"
|
||||
|
||||
func (s storage) RegistryFind(repo *model.Repo, addr string) (*model.Registry, error) {
|
||||
reg := new(model.Registry)
|
||||
return reg, wrapGet(s.engine.Where(
|
||||
|
@ -27,9 +29,19 @@ func (s storage) RegistryFind(repo *model.Repo, addr string) (*model.Registry, e
|
|||
).Get(reg))
|
||||
}
|
||||
|
||||
func (s storage) RegistryList(repo *model.Repo, p *model.ListOptions) ([]*model.Registry, error) {
|
||||
func (s storage) RegistryList(repo *model.Repo, includeGlobalAndOrg bool, p *model.ListOptions) ([]*model.Registry, error) {
|
||||
var regs []*model.Registry
|
||||
return regs, s.paginate(p).OrderBy("id").Where("repo_id = ?", repo.ID).Find(®s)
|
||||
var cond builder.Cond = builder.Eq{"repo_id": repo.ID}
|
||||
if includeGlobalAndOrg {
|
||||
cond = cond.Or(builder.Eq{"org_id": repo.OrgID}).
|
||||
Or(builder.And(builder.Eq{"org_id": 0}, builder.Eq{"repo_id": 0}))
|
||||
}
|
||||
return regs, s.paginate(p).Where(cond).OrderBy(orderRegistriesBy).Find(®s)
|
||||
}
|
||||
|
||||
func (s storage) RegistryListAll() ([]*model.Registry, error) {
|
||||
var registries []*model.Registry
|
||||
return registries, s.engine.Find(®istries)
|
||||
}
|
||||
|
||||
func (s storage) RegistryCreate(registry *model.Registry) error {
|
||||
|
@ -43,10 +55,32 @@ func (s storage) RegistryUpdate(registry *model.Registry) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func (s storage) RegistryDelete(repo *model.Repo, addr string) error {
|
||||
registry, err := s.RegistryFind(repo, addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
func (s storage) RegistryDelete(registry *model.Registry) error {
|
||||
return wrapDelete(s.engine.ID(registry.ID).Delete(new(model.Registry)))
|
||||
}
|
||||
|
||||
func (s storage) OrgRegistryFind(orgID int64, name string) (*model.Registry, error) {
|
||||
registry := new(model.Registry)
|
||||
return registry, wrapGet(s.engine.Where(
|
||||
builder.Eq{"org_id": orgID, "address": name},
|
||||
).Get(registry))
|
||||
}
|
||||
|
||||
func (s storage) OrgRegistryList(orgID int64, p *model.ListOptions) ([]*model.Registry, error) {
|
||||
registries := make([]*model.Registry, 0)
|
||||
return registries, s.paginate(p).Where("org_id = ?", orgID).OrderBy(orderRegistriesBy).Find(®istries)
|
||||
}
|
||||
|
||||
func (s storage) GlobalRegistryFind(name string) (*model.Registry, error) {
|
||||
registry := new(model.Registry)
|
||||
return registry, wrapGet(s.engine.Where(
|
||||
builder.Eq{"org_id": 0, "repo_id": 0, "address": name},
|
||||
).Get(registry))
|
||||
}
|
||||
|
||||
func (s storage) GlobalRegistryList(p *model.ListOptions) ([]*model.Registry, error) {
|
||||
registries := make([]*model.Registry, 0)
|
||||
return registries, s.paginate(p).Where(
|
||||
builder.Eq{"org_id": 0, "repo_id": 0},
|
||||
).OrderBy(orderRegistriesBy).Find(®istries)
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/store/types"
|
||||
|
@ -60,7 +61,7 @@ func TestRegistryList(t *testing.T) {
|
|||
Password: "bar",
|
||||
}))
|
||||
|
||||
list, err := store.RegistryList(&model.Repo{ID: 1}, &model.ListOptions{Page: 1, PerPage: 50})
|
||||
list, err := store.RegistryList(&model.Repo{ID: 1}, false, &model.ListOptions{Page: 1, PerPage: 50})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, list, 2)
|
||||
}
|
||||
|
@ -117,6 +118,88 @@ func TestRegistryDelete(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, store.RegistryDelete(&model.Repo{ID: 1}, "index.docker.io"))
|
||||
assert.ErrorIs(t, store.RegistryDelete(&model.Repo{ID: 1}, "index.docker.io"), types.RecordNotExist)
|
||||
assert.NoError(t, store.RegistryDelete(reg1))
|
||||
assert.ErrorIs(t, store.RegistryDelete(reg1), types.RecordNotExist)
|
||||
}
|
||||
|
||||
func createTestRegistries(t *testing.T, store *storage) {
|
||||
assert.NoError(t, store.RegistryCreate(&model.Registry{
|
||||
OrgID: 12,
|
||||
Address: "my.regsitry.local",
|
||||
}))
|
||||
assert.NoError(t, store.RegistryCreate(&model.Registry{
|
||||
RepoID: 1,
|
||||
Address: "private.registry.local",
|
||||
}))
|
||||
assert.NoError(t, store.RegistryCreate(&model.Registry{
|
||||
RepoID: 1,
|
||||
Address: "very-private.registry.local",
|
||||
}))
|
||||
assert.NoError(t, store.RegistryCreate(&model.Registry{
|
||||
Address: "index.docker.io",
|
||||
}))
|
||||
}
|
||||
|
||||
func TestOrgRegistryFind(t *testing.T) {
|
||||
store, closer := newTestStore(t, new(model.Registry))
|
||||
defer closer()
|
||||
|
||||
err := store.RegistryCreate(&model.Registry{
|
||||
OrgID: 12,
|
||||
Address: "my.regsitry.local",
|
||||
Username: "username",
|
||||
Password: "password",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
registry, err := store.OrgRegistryFind(12, "my.regsitry.local")
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 12, registry.OrgID)
|
||||
assert.Equal(t, "my.regsitry.local", registry.Address)
|
||||
assert.Equal(t, "username", registry.Username)
|
||||
assert.Equal(t, "password", registry.Password)
|
||||
}
|
||||
|
||||
func TestOrgRegistryList(t *testing.T) {
|
||||
store, closer := newTestStore(t, new(model.Registry))
|
||||
defer closer()
|
||||
|
||||
createTestRegistries(t, store)
|
||||
|
||||
list, err := store.OrgRegistryList(12, &model.ListOptions{All: true})
|
||||
assert.NoError(t, err)
|
||||
require.Len(t, list, 1)
|
||||
|
||||
assert.True(t, list[0].IsOrganization())
|
||||
}
|
||||
|
||||
func TestGlobalRegistryFind(t *testing.T) {
|
||||
store, closer := newTestStore(t, new(model.Registry))
|
||||
defer closer()
|
||||
|
||||
err := store.RegistryCreate(&model.Registry{
|
||||
Address: "my.regsitry.local",
|
||||
Username: "username",
|
||||
Password: "password",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
registry, err := store.GlobalRegistryFind("my.regsitry.local")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "my.regsitry.local", registry.Address)
|
||||
assert.Equal(t, "username", registry.Username)
|
||||
assert.Equal(t, "password", registry.Password)
|
||||
}
|
||||
|
||||
func TestGlobalRegistryList(t *testing.T) {
|
||||
store, closer := newTestStore(t, new(model.Registry))
|
||||
defer closer()
|
||||
|
||||
createTestRegistries(t, store)
|
||||
|
||||
list, err := store.GlobalRegistryList(&model.ListOptions{All: true})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, list, 1)
|
||||
|
||||
assert.True(t, list[0].IsGlobal())
|
||||
}
|
||||
|
|
|
@ -1190,6 +1190,66 @@ func (_m *Store) GetUserRemoteID(_a0 model.ForgeRemoteID, _a1 string) (*model.Us
|
|||
return r0, r1
|
||||
}
|
||||
|
||||
// GlobalRegistryFind provides a mock function with given fields: _a0
|
||||
func (_m *Store) GlobalRegistryFind(_a0 string) (*model.Registry, error) {
|
||||
ret := _m.Called(_a0)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GlobalRegistryFind")
|
||||
}
|
||||
|
||||
var r0 *model.Registry
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string) (*model.Registry, error)); ok {
|
||||
return rf(_a0)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string) *model.Registry); ok {
|
||||
r0 = rf(_a0)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.Registry)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(_a0)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GlobalRegistryList provides a mock function with given fields: _a0
|
||||
func (_m *Store) GlobalRegistryList(_a0 *model.ListOptions) ([]*model.Registry, error) {
|
||||
ret := _m.Called(_a0)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GlobalRegistryList")
|
||||
}
|
||||
|
||||
var r0 []*model.Registry
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(*model.ListOptions) ([]*model.Registry, error)); ok {
|
||||
return rf(_a0)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(*model.ListOptions) []*model.Registry); ok {
|
||||
r0 = rf(_a0)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.Registry)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(*model.ListOptions) error); ok {
|
||||
r1 = rf(_a0)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GlobalSecretFind provides a mock function with given fields: _a0
|
||||
func (_m *Store) GlobalSecretFind(_a0 string) (*model.Secret, error) {
|
||||
ret := _m.Called(_a0)
|
||||
|
@ -1488,6 +1548,66 @@ func (_m *Store) OrgList(_a0 *model.ListOptions) ([]*model.Org, error) {
|
|||
return r0, r1
|
||||
}
|
||||
|
||||
// OrgRegistryFind provides a mock function with given fields: _a0, _a1
|
||||
func (_m *Store) OrgRegistryFind(_a0 int64, _a1 string) (*model.Registry, error) {
|
||||
ret := _m.Called(_a0, _a1)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for OrgRegistryFind")
|
||||
}
|
||||
|
||||
var r0 *model.Registry
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(int64, string) (*model.Registry, error)); ok {
|
||||
return rf(_a0, _a1)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(int64, string) *model.Registry); ok {
|
||||
r0 = rf(_a0, _a1)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.Registry)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(int64, string) error); ok {
|
||||
r1 = rf(_a0, _a1)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// OrgRegistryList provides a mock function with given fields: _a0, _a1
|
||||
func (_m *Store) OrgRegistryList(_a0 int64, _a1 *model.ListOptions) ([]*model.Registry, error) {
|
||||
ret := _m.Called(_a0, _a1)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for OrgRegistryList")
|
||||
}
|
||||
|
||||
var r0 []*model.Registry
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(int64, *model.ListOptions) ([]*model.Registry, error)); ok {
|
||||
return rf(_a0, _a1)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(int64, *model.ListOptions) []*model.Registry); ok {
|
||||
r0 = rf(_a0, _a1)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.Registry)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(int64, *model.ListOptions) error); ok {
|
||||
r1 = rf(_a0, _a1)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// OrgRepoList provides a mock function with given fields: _a0, _a1
|
||||
func (_m *Store) OrgRepoList(_a0 *model.Org, _a1 *model.ListOptions) ([]*model.Repo, error) {
|
||||
ret := _m.Called(_a0, _a1)
|
||||
|
@ -1698,17 +1818,17 @@ func (_m *Store) RegistryCreate(_a0 *model.Registry) error {
|
|||
return r0
|
||||
}
|
||||
|
||||
// RegistryDelete provides a mock function with given fields: repo, addr
|
||||
func (_m *Store) RegistryDelete(repo *model.Repo, addr string) error {
|
||||
ret := _m.Called(repo, addr)
|
||||
// RegistryDelete provides a mock function with given fields: _a0
|
||||
func (_m *Store) RegistryDelete(_a0 *model.Registry) error {
|
||||
ret := _m.Called(_a0)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for RegistryDelete")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(*model.Repo, string) error); ok {
|
||||
r0 = rf(repo, addr)
|
||||
if rf, ok := ret.Get(0).(func(*model.Registry) error); ok {
|
||||
r0 = rf(_a0)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
@ -1746,9 +1866,9 @@ func (_m *Store) RegistryFind(_a0 *model.Repo, _a1 string) (*model.Registry, err
|
|||
return r0, r1
|
||||
}
|
||||
|
||||
// RegistryList provides a mock function with given fields: _a0, _a1
|
||||
func (_m *Store) RegistryList(_a0 *model.Repo, _a1 *model.ListOptions) ([]*model.Registry, error) {
|
||||
ret := _m.Called(_a0, _a1)
|
||||
// RegistryList provides a mock function with given fields: _a0, _a1, _a2
|
||||
func (_m *Store) RegistryList(_a0 *model.Repo, _a1 bool, _a2 *model.ListOptions) ([]*model.Registry, error) {
|
||||
ret := _m.Called(_a0, _a1, _a2)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for RegistryList")
|
||||
|
@ -1756,19 +1876,49 @@ func (_m *Store) RegistryList(_a0 *model.Repo, _a1 *model.ListOptions) ([]*model
|
|||
|
||||
var r0 []*model.Registry
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(*model.Repo, *model.ListOptions) ([]*model.Registry, error)); ok {
|
||||
return rf(_a0, _a1)
|
||||
if rf, ok := ret.Get(0).(func(*model.Repo, bool, *model.ListOptions) ([]*model.Registry, error)); ok {
|
||||
return rf(_a0, _a1, _a2)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(*model.Repo, *model.ListOptions) []*model.Registry); ok {
|
||||
r0 = rf(_a0, _a1)
|
||||
if rf, ok := ret.Get(0).(func(*model.Repo, bool, *model.ListOptions) []*model.Registry); ok {
|
||||
r0 = rf(_a0, _a1, _a2)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.Registry)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(*model.Repo, *model.ListOptions) error); ok {
|
||||
r1 = rf(_a0, _a1)
|
||||
if rf, ok := ret.Get(1).(func(*model.Repo, bool, *model.ListOptions) error); ok {
|
||||
r1 = rf(_a0, _a1, _a2)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// RegistryListAll provides a mock function with given fields:
|
||||
func (_m *Store) RegistryListAll() ([]*model.Registry, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for RegistryListAll")
|
||||
}
|
||||
|
||||
var r0 []*model.Registry
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func() ([]*model.Registry, error)); ok {
|
||||
return rf()
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func() []*model.Registry); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.Registry)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
|
|
@ -120,10 +120,15 @@ type Store interface {
|
|||
|
||||
// Registries
|
||||
RegistryFind(*model.Repo, string) (*model.Registry, error)
|
||||
RegistryList(*model.Repo, *model.ListOptions) ([]*model.Registry, error)
|
||||
RegistryList(*model.Repo, bool, *model.ListOptions) ([]*model.Registry, error)
|
||||
RegistryListAll() ([]*model.Registry, error)
|
||||
RegistryCreate(*model.Registry) error
|
||||
RegistryUpdate(*model.Registry) error
|
||||
RegistryDelete(repo *model.Repo, addr string) error
|
||||
RegistryDelete(*model.Registry) error
|
||||
OrgRegistryFind(int64, string) (*model.Registry, error)
|
||||
OrgRegistryList(int64, *model.ListOptions) ([]*model.Registry, error)
|
||||
GlobalRegistryFind(string) (*model.Registry, error)
|
||||
GlobalRegistryList(*model.ListOptions) ([]*model.Registry, error)
|
||||
|
||||
// Steps
|
||||
StepLoad(int64) (*model.Step, error)
|
||||
|
|
6
web/components.d.ts
vendored
6
web/components.d.ts
vendored
|
@ -14,6 +14,7 @@ declare module 'vue' {
|
|||
AdminOrgsTab: typeof import('./src/components/admin/settings/AdminOrgsTab.vue')['default']
|
||||
AdminQueueStats: typeof import('./src/components/admin/settings/queue/AdminQueueStats.vue')['default']
|
||||
AdminQueueTab: typeof import('./src/components/admin/settings/AdminQueueTab.vue')['default']
|
||||
AdminRegistriesTab: typeof import('./src/components/admin/settings/AdminRegistriesTab.vue')['default']
|
||||
AdminReposTab: typeof import('./src/components/admin/settings/AdminReposTab.vue')['default']
|
||||
AdminSecretsTab: typeof import('./src/components/admin/settings/AdminSecretsTab.vue')['default']
|
||||
AdminUsersTab: typeof import('./src/components/admin/settings/AdminUsersTab.vue')['default']
|
||||
|
@ -66,12 +67,12 @@ declare module 'vue' {
|
|||
IMdiPlay: typeof import('~icons/mdi/play')['default']
|
||||
IMdiRadioboxBlank: typeof import('~icons/mdi/radiobox-blank')['default']
|
||||
IMdiRadioboxIndeterminateVariant: typeof import('~icons/mdi/radiobox-indeterminate-variant')['default']
|
||||
IMdiSync: typeof import('~icons/mdi/sync')['default']
|
||||
IMdiSourceBranch: typeof import('~icons/mdi/source-branch')['default']
|
||||
IMdiSourceCommit: typeof import('~icons/mdi/source-commit')['default']
|
||||
IMdiSourceMerge: typeof import('~icons/mdi/source-merge')['default']
|
||||
IMdiSourcePull: typeof import('~icons/mdi/source-pull')['default']
|
||||
IMdiStop: typeof import('~icons/mdi/stop')['default']
|
||||
IMdiSync: typeof import('~icons/mdi/sync')['default']
|
||||
IMdiTagOutline: typeof import('~icons/mdi/tag-outline')['default']
|
||||
InputField: typeof import('./src/components/form/InputField.vue')['default']
|
||||
IPhGitlabLogoSimpleFill: typeof import('~icons/ph/gitlab-logo-simple-fill')['default']
|
||||
|
@ -85,6 +86,7 @@ declare module 'vue' {
|
|||
ManualPipelinePopup: typeof import('./src/components/layout/popups/ManualPipelinePopup.vue')['default']
|
||||
Navbar: typeof import('./src/components/layout/header/Navbar.vue')['default']
|
||||
NumberField: typeof import('./src/components/form/NumberField.vue')['default']
|
||||
OrgRegistriesTab: typeof import('./src/components/org/settings/OrgRegistriesTab.vue')['default']
|
||||
OrgSecretsTab: typeof import('./src/components/org/settings/OrgSecretsTab.vue')['default']
|
||||
Panel: typeof import('./src/components/layout/Panel.vue')['default']
|
||||
PipelineFeedItem: typeof import('./src/components/pipeline-feed/PipelineFeedItem.vue')['default']
|
||||
|
@ -98,7 +100,9 @@ declare module 'vue' {
|
|||
PipelineStepList: typeof import('./src/components/repo/pipeline/PipelineStepList.vue')['default']
|
||||
Popup: typeof import('./src/components/layout/Popup.vue')['default']
|
||||
RadioField: typeof import('./src/components/form/RadioField.vue')['default']
|
||||
RegistryEdit: typeof import('./src/components/registry/RegistryEdit.vue')['default']
|
||||
RegistriesTab: typeof import('./src/components/repo/settings/RegistriesTab.vue')['default']
|
||||
RegistryList: typeof import('./src/components/registry/RegistryList.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
Scaffold: typeof import('./src/components/layout/scaffold/Scaffold.vue')['default']
|
||||
|
|
|
@ -122,24 +122,6 @@
|
|||
"desc": "Enable to cancel pending and running pipelines of the same event and context before starting the newly triggered one."
|
||||
}
|
||||
},
|
||||
"registries": {
|
||||
"registries": "Registries",
|
||||
"credentials": "Registry credentials",
|
||||
"desc": "Registries credentials can be added to use private images for your pipeline.",
|
||||
"show": "Show registries",
|
||||
"add": "Add registry",
|
||||
"none": "There are no registry credentials yet.",
|
||||
"save": "Save registry",
|
||||
"created": "Registry credentials created",
|
||||
"saved": "Registry credentials saved",
|
||||
"deleted": "Registry credentials deleted",
|
||||
"address": {
|
||||
"address": "Address",
|
||||
"placeholder": "Registry Address (e.g. docker.io)"
|
||||
},
|
||||
"edit": "Edit registry",
|
||||
"delete": "Delete registry"
|
||||
},
|
||||
"crons": {
|
||||
"crons": "Crons",
|
||||
"desc": "Cron jobs can be used to trigger pipelines on a regular basis.",
|
||||
|
@ -266,6 +248,9 @@
|
|||
"not_allowed": "You are not allowed to access this organization's settings",
|
||||
"secrets": {
|
||||
"desc": "Organization secrets can be passed to all organization's repository individual pipeline steps at runtime as environmental variables."
|
||||
},
|
||||
"registries": {
|
||||
"desc": "Organization registry credentials can be added to use private images for all organization's pipelines."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -276,6 +261,10 @@
|
|||
"desc": "Global secrets can be passed to all repositories individual pipeline steps at runtime as environmental variables.",
|
||||
"warning": "These secrets will be available for all server users."
|
||||
},
|
||||
"registries": {
|
||||
"desc": "Global registry credentials can be added to use private images for all server's pipelines.",
|
||||
"warning": "These registry creditentials will be available for all server users."
|
||||
},
|
||||
"agents": {
|
||||
"agents": "Agents",
|
||||
"desc": "Agents registered for this server",
|
||||
|
@ -435,6 +424,26 @@
|
|||
"edit": "Edit secret",
|
||||
"delete": "Delete secret"
|
||||
},
|
||||
"registries": {
|
||||
"registries": "Registries",
|
||||
"credentials": "Registry credentials",
|
||||
"desc": "Registries credentials can be added to use private images for your pipeline.",
|
||||
"none": "There are no registry credentials yet.",
|
||||
"address": {
|
||||
"address": "Address",
|
||||
"desc": "Registry Address (e.g. docker.io)"
|
||||
},
|
||||
"show": "Show registries",
|
||||
"save": "Save registry",
|
||||
"add": "Add registry",
|
||||
"view": "View registry",
|
||||
"edit": "Edit registry",
|
||||
"delete": "Delete registry",
|
||||
"delete_confirm": "Do you really want to delete this registry?",
|
||||
"created": "Registry credentials created",
|
||||
"saved": "Registry credentials saved",
|
||||
"deleted": "Registry credentials deleted"
|
||||
},
|
||||
"default": "default",
|
||||
"info": "Info",
|
||||
"running_version": "You are running Woodpecker {0}",
|
||||
|
|
101
web/src/components/admin/settings/AdminRegistriesTab.vue
Normal file
101
web/src/components/admin/settings/AdminRegistriesTab.vue
Normal file
|
@ -0,0 +1,101 @@
|
|||
<template>
|
||||
<Settings
|
||||
:title="$t('registries.registries')"
|
||||
:desc="$t('admin.settings.registries.desc')"
|
||||
docs-url="docs/usage/registries"
|
||||
:warning="$t('admin.settings.registries.warning')"
|
||||
>
|
||||
<template #titleActions>
|
||||
<Button
|
||||
v-if="selectedRegistry"
|
||||
:text="$t('registries.show')"
|
||||
start-icon="back"
|
||||
@click="selectedRegistry = undefined"
|
||||
/>
|
||||
<Button v-else :text="$t('registries.add')" start-icon="plus" @click="showAddRegistry" />
|
||||
</template>
|
||||
|
||||
<RegistryList
|
||||
v-if="!selectedRegistry"
|
||||
v-model="registries"
|
||||
:is-deleting="isDeleting"
|
||||
@edit="editRegistry"
|
||||
@delete="deleteRegistry"
|
||||
/>
|
||||
|
||||
<RegistryEdit
|
||||
v-else
|
||||
v-model="selectedRegistry"
|
||||
:is-saving="isSaving"
|
||||
@save="createRegistry"
|
||||
@cancel="selectedRegistry = undefined"
|
||||
/>
|
||||
</Settings>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import Button from '~/components/atomic/Button.vue';
|
||||
import Settings from '~/components/layout/Settings.vue';
|
||||
import RegistryEdit from '~/components/registry/RegistryEdit.vue';
|
||||
import RegistryList from '~/components/registry/RegistryList.vue';
|
||||
import useApiClient from '~/compositions/useApiClient';
|
||||
import { useAsyncAction } from '~/compositions/useAsyncAction';
|
||||
import useNotifications from '~/compositions/useNotifications';
|
||||
import { usePagination } from '~/compositions/usePaginate';
|
||||
import type { Registry } from '~/lib/api/types';
|
||||
|
||||
const emptyRegistry: Partial<Registry> = {
|
||||
address: '',
|
||||
username: '',
|
||||
password: '',
|
||||
};
|
||||
|
||||
const apiClient = useApiClient();
|
||||
const notifications = useNotifications();
|
||||
const i18n = useI18n();
|
||||
|
||||
const selectedRegistry = ref<Partial<Registry>>();
|
||||
const isEditingRegistry = computed(() => !!selectedRegistry.value?.id);
|
||||
|
||||
async function loadRegistries(page: number): Promise<Registry[] | null> {
|
||||
return apiClient.getGlobalRegistryList({ page });
|
||||
}
|
||||
|
||||
const { resetPage, data: registries } = usePagination(loadRegistries, () => !selectedRegistry.value);
|
||||
|
||||
const { doSubmit: createRegistry, isLoading: isSaving } = useAsyncAction(async () => {
|
||||
if (!selectedRegistry.value) {
|
||||
throw new Error("Unexpected: Can't get registry");
|
||||
}
|
||||
|
||||
if (isEditingRegistry.value) {
|
||||
await apiClient.updateGlobalRegistry(selectedRegistry.value);
|
||||
} else {
|
||||
await apiClient.createGlobalRegistry(selectedRegistry.value);
|
||||
}
|
||||
notifications.notify({
|
||||
title: isEditingRegistry.value ? i18n.t('registries.saved') : i18n.t('registries.created'),
|
||||
type: 'success',
|
||||
});
|
||||
selectedRegistry.value = undefined;
|
||||
resetPage();
|
||||
});
|
||||
|
||||
const { doSubmit: deleteRegistry, isLoading: isDeleting } = useAsyncAction(async (_registry: Registry) => {
|
||||
await apiClient.deleteGlobalRegistry(_registry.address);
|
||||
notifications.notify({ title: i18n.t('registries.deleted'), type: 'success' });
|
||||
resetPage();
|
||||
});
|
||||
|
||||
function editRegistry(registry: Registry) {
|
||||
selectedRegistry.value = cloneDeep(registry);
|
||||
}
|
||||
|
||||
function showAddRegistry() {
|
||||
selectedRegistry.value = cloneDeep(emptyRegistry);
|
||||
}
|
||||
</script>
|
|
@ -3,7 +3,7 @@
|
|||
<button
|
||||
v-for="tab in tabs"
|
||||
:key="tab.id"
|
||||
class="w-full py-1 md:py-2 md:w-auto md:px-8 flex cursor-pointer md:border-b-2 text-wp-text-100 hover:text-wp-text-200 items-center"
|
||||
class="w-full py-1 md:py-2 md:w-auto md:px-6 flex cursor-pointer md:border-b-2 text-wp-text-100 hover:text-wp-text-200 items-center"
|
||||
:class="{
|
||||
'border-wp-text-100': activeTab === tab.id,
|
||||
'border-transparent': activeTab !== tab.id,
|
||||
|
|
113
web/src/components/org/settings/OrgRegistriesTab.vue
Normal file
113
web/src/components/org/settings/OrgRegistriesTab.vue
Normal file
|
@ -0,0 +1,113 @@
|
|||
<template>
|
||||
<Settings
|
||||
:title="$t('registries.registries')"
|
||||
:desc="$t('org.settings.registries.desc')"
|
||||
docs-url="docs/usage/registries"
|
||||
>
|
||||
<template #titleActions>
|
||||
<Button
|
||||
v-if="selectedRegistry"
|
||||
:text="$t('registries.show')"
|
||||
start-icon="back"
|
||||
@click="selectedRegistry = undefined"
|
||||
/>
|
||||
<Button v-else :text="$t('registries.add')" start-icon="plus" @click="showAddRegistry" />
|
||||
</template>
|
||||
|
||||
<RegistryList
|
||||
v-if="!selectedRegistry"
|
||||
v-model="registries"
|
||||
:is-deleting="isDeleting"
|
||||
@edit="editRegistry"
|
||||
@delete="deleteRegistry"
|
||||
/>
|
||||
|
||||
<RegistryEdit
|
||||
v-else
|
||||
v-model="selectedRegistry"
|
||||
:is-saving="isSaving"
|
||||
@save="createRegistry"
|
||||
@cancel="selectedRegistry = undefined"
|
||||
/>
|
||||
</Settings>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { computed, inject, ref, type Ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import Button from '~/components/atomic/Button.vue';
|
||||
import Settings from '~/components/layout/Settings.vue';
|
||||
import RegistryEdit from '~/components/registry/RegistryEdit.vue';
|
||||
import RegistryList from '~/components/registry/RegistryList.vue';
|
||||
import useApiClient from '~/compositions/useApiClient';
|
||||
import { useAsyncAction } from '~/compositions/useAsyncAction';
|
||||
import useNotifications from '~/compositions/useNotifications';
|
||||
import { usePagination } from '~/compositions/usePaginate';
|
||||
import type { Org, Registry } from '~/lib/api/types';
|
||||
|
||||
const emptyRegistry: Partial<Registry> = {
|
||||
address: '',
|
||||
username: '',
|
||||
password: '',
|
||||
};
|
||||
|
||||
const apiClient = useApiClient();
|
||||
const notifications = useNotifications();
|
||||
const i18n = useI18n();
|
||||
|
||||
const org = inject<Ref<Org>>('org');
|
||||
const selectedRegistry = ref<Partial<Registry>>();
|
||||
const isEditing = computed(() => !!selectedRegistry.value?.id);
|
||||
|
||||
async function loadRegistries(page: number): Promise<Registry[] | null> {
|
||||
if (!org?.value) {
|
||||
throw new Error("Unexpected: Can't load org");
|
||||
}
|
||||
|
||||
return apiClient.getOrgRegistryList(org.value.id, { page });
|
||||
}
|
||||
|
||||
const { resetPage, data: registries } = usePagination(loadRegistries, () => !selectedRegistry.value);
|
||||
|
||||
const { doSubmit: createRegistry, isLoading: isSaving } = useAsyncAction(async () => {
|
||||
if (!org?.value) {
|
||||
throw new Error("Unexpected: Can't load org");
|
||||
}
|
||||
|
||||
if (!selectedRegistry.value) {
|
||||
throw new Error("Unexpected: Can't get registry");
|
||||
}
|
||||
|
||||
if (isEditing.value) {
|
||||
await apiClient.updateOrgRegistry(org.value.id, selectedRegistry.value);
|
||||
} else {
|
||||
await apiClient.createOrgRegistry(org.value.id, selectedRegistry.value);
|
||||
}
|
||||
notifications.notify({
|
||||
title: isEditing.value ? i18n.t('registries.saved') : i18n.t('registries.created'),
|
||||
type: 'success',
|
||||
});
|
||||
selectedRegistry.value = undefined;
|
||||
resetPage();
|
||||
});
|
||||
|
||||
const { doSubmit: deleteRegistry, isLoading: isDeleting } = useAsyncAction(async (_registry: Registry) => {
|
||||
if (!org?.value) {
|
||||
throw new Error("Unexpected: Can't load org");
|
||||
}
|
||||
|
||||
await apiClient.deleteOrgRegistry(org.value.id, _registry.address);
|
||||
notifications.notify({ title: i18n.t('registries.deleted'), type: 'success' });
|
||||
resetPage();
|
||||
});
|
||||
|
||||
function editRegistry(registry: Registry) {
|
||||
selectedRegistry.value = cloneDeep(registry);
|
||||
}
|
||||
|
||||
function showAddRegistry() {
|
||||
selectedRegistry.value = cloneDeep(emptyRegistry);
|
||||
}
|
||||
</script>
|
78
web/src/components/registry/RegistryEdit.vue
Normal file
78
web/src/components/registry/RegistryEdit.vue
Normal file
|
@ -0,0 +1,78 @@
|
|||
<template>
|
||||
<div v-if="innerValue" class="space-y-4">
|
||||
<form @submit.prevent="save">
|
||||
<InputField v-slot="{ id }" :label="$t('registries.address.address')">
|
||||
<!-- TODO: check input field Address is a valid address -->
|
||||
<TextField
|
||||
:id="id"
|
||||
v-model="innerValue.address"
|
||||
:placeholder="$t('registries.address.desc')"
|
||||
required
|
||||
:disabled="isEditing || isReadOnly"
|
||||
/>
|
||||
</InputField>
|
||||
|
||||
<InputField v-slot="{ id }" :label="$t('username')">
|
||||
<TextField
|
||||
:id="id"
|
||||
v-model="innerValue.username"
|
||||
:placeholder="$t('username')"
|
||||
required
|
||||
:disabled="isReadOnly"
|
||||
/>
|
||||
</InputField>
|
||||
|
||||
<InputField v-if="!isReadOnly" v-slot="{ id }" :label="$t('password')">
|
||||
<TextField :id="id" v-model="innerValue.password" :placeholder="$t('password')" :required="!isEditing" />
|
||||
</InputField>
|
||||
|
||||
<div v-if="!isReadOnly" class="flex gap-2">
|
||||
<Button type="button" color="gray" :text="$t('cancel')" @click="$emit('cancel')" />
|
||||
<Button
|
||||
type="submit"
|
||||
color="green"
|
||||
:is-loading="isSaving"
|
||||
:text="isEditing ? $t('registries.save') : $t('registries.add')"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, toRef } from 'vue';
|
||||
|
||||
import Button from '~/components/atomic/Button.vue';
|
||||
import InputField from '~/components/form/InputField.vue';
|
||||
import TextField from '~/components/form/TextField.vue';
|
||||
import type { Registry } from '~/lib/api/types';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: Partial<Registry>;
|
||||
isSaving: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:modelValue', value: Partial<Registry> | undefined): void;
|
||||
(event: 'save', value: Partial<Registry>): void;
|
||||
(event: 'cancel'): void;
|
||||
}>();
|
||||
|
||||
const modelValue = toRef(props, 'modelValue');
|
||||
const innerValue = computed({
|
||||
get: () => modelValue.value,
|
||||
set: (value) => {
|
||||
emit('update:modelValue', value);
|
||||
},
|
||||
});
|
||||
const isEditing = computed(() => !!innerValue.value?.id);
|
||||
const isReadOnly = computed(() => !!innerValue.value?.readonly);
|
||||
|
||||
function save() {
|
||||
if (!innerValue.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit('save', innerValue.value);
|
||||
}
|
||||
</script>
|
63
web/src/components/registry/RegistryList.vue
Normal file
63
web/src/components/registry/RegistryList.vue
Normal file
|
@ -0,0 +1,63 @@
|
|||
<template>
|
||||
<div class="space-y-4 text-wp-text-100">
|
||||
<ListItem
|
||||
v-for="registry in registries"
|
||||
:key="registry.id"
|
||||
class="items-center !bg-wp-background-200 !dark:bg-wp-background-100"
|
||||
>
|
||||
<span>{{ registry.address }}</span>
|
||||
<IconButton
|
||||
:icon="registry.readonly ? 'chevron-right' : 'edit'"
|
||||
class="ml-auto w-8 h-8"
|
||||
:title="registry.readonly ? $t('registries.view') : $t('registries.edit')"
|
||||
@click="editRegistry(registry)"
|
||||
/>
|
||||
<IconButton
|
||||
v-if="!registry.readonly"
|
||||
icon="trash"
|
||||
class="w-8 h-8 hover:text-wp-control-error-100"
|
||||
:is-loading="isDeleting"
|
||||
:title="$t('registries.delete')"
|
||||
@click="deleteRegistry(registry)"
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
<div v-if="registries?.length === 0" class="ml-2">{{ $t('registries.none') }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { toRef } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import IconButton from '~/components/atomic/IconButton.vue';
|
||||
import ListItem from '~/components/atomic/ListItem.vue';
|
||||
import type { Registry } from '~/lib/api/types';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: (Registry & { edit?: boolean })[];
|
||||
isDeleting: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'edit', registry: Registry): void;
|
||||
(event: 'delete', registry: Registry): void;
|
||||
}>();
|
||||
|
||||
const i18n = useI18n();
|
||||
|
||||
const registries = toRef(props, 'modelValue');
|
||||
|
||||
function editRegistry(registry: Registry) {
|
||||
emit('edit', registry);
|
||||
}
|
||||
|
||||
function deleteRegistry(registry: Registry) {
|
||||
// TODO: use proper dialog
|
||||
// eslint-disable-next-line no-alert
|
||||
if (!confirm(i18n.t('registries.delete_confirm'))) {
|
||||
return;
|
||||
}
|
||||
emit('delete', registry);
|
||||
}
|
||||
</script>
|
|
@ -1,95 +1,54 @@
|
|||
<template>
|
||||
<Settings
|
||||
:title="$t('repo.settings.registries.credentials')"
|
||||
:desc="$t('repo.settings.registries.desc')"
|
||||
docs-url="docs/usage/registries"
|
||||
>
|
||||
<Settings :title="$t('registries.credentials')" :desc="$t('registries.desc')" docs-url="docs/usage/registries">
|
||||
<template #titleActions>
|
||||
<Button
|
||||
v-if="selectedRegistry"
|
||||
:text="$t('registries.show')"
|
||||
start-icon="back"
|
||||
:text="$t('repo.settings.registries.show')"
|
||||
@click="selectedRegistry = undefined"
|
||||
/>
|
||||
<Button v-else start-icon="plus" :text="$t('repo.settings.registries.add')" @click="selectedRegistry = {}" />
|
||||
<Button v-else :text="$t('registries.add')" start-icon="plus" @click="showAddRegistry" />
|
||||
</template>
|
||||
|
||||
<div v-if="!selectedRegistry" class="space-y-4 text-wp-text-100">
|
||||
<ListItem
|
||||
v-for="registry in registries"
|
||||
:key="registry.id"
|
||||
class="items-center !bg-wp-background-200 !dark:bg-wp-background-100"
|
||||
>
|
||||
<span>{{ registry.address }}</span>
|
||||
<IconButton
|
||||
icon="edit"
|
||||
class="ml-auto w-8 h-8"
|
||||
:title="$t('repo.settings.registries.edit')"
|
||||
@click="selectedRegistry = registry"
|
||||
/>
|
||||
<IconButton
|
||||
icon="trash"
|
||||
class="w-8 h-8 hover:text-wp-control-error-100"
|
||||
:is-loading="isDeleting"
|
||||
:title="$t('repo.settings.registries.delete')"
|
||||
@click="deleteRegistry(registry)"
|
||||
/>
|
||||
</ListItem>
|
||||
<RegistryList
|
||||
v-if="!selectedRegistry"
|
||||
v-model="registries"
|
||||
:is-deleting="isDeleting"
|
||||
@edit="editRegistry"
|
||||
@delete="deleteRegistry"
|
||||
/>
|
||||
|
||||
<div v-if="registries?.length === 0" class="ml-2">{{ $t('repo.settings.registries.none') }}</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="space-y-4">
|
||||
<form @submit.prevent="createRegistry">
|
||||
<InputField v-slot="{ id }" :label="$t('repo.settings.registries.address.address')">
|
||||
<!-- TODO: check input field Address is a valid address -->
|
||||
<TextField
|
||||
:id="id"
|
||||
v-model="selectedRegistry.address"
|
||||
:placeholder="$t('repo.settings.registries.address.placeholder')"
|
||||
required
|
||||
:disabled="isEditingRegistry"
|
||||
/>
|
||||
</InputField>
|
||||
|
||||
<InputField v-slot="{ id }" :label="$t('username')">
|
||||
<TextField :id="id" v-model="selectedRegistry.username" :placeholder="$t('username')" required />
|
||||
</InputField>
|
||||
|
||||
<InputField v-slot="{ id }" :label="$t('password')">
|
||||
<TextField :id="id" v-model="selectedRegistry.password" :placeholder="$t('password')" required />
|
||||
</InputField>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<Button type="button" color="gray" :text="$t('cancel')" @click="selectedRegistry = undefined" />
|
||||
<Button
|
||||
type="submit"
|
||||
color="green"
|
||||
:is-loading="isSaving"
|
||||
:text="isEditingRegistry ? $t('repo.settings.registries.save') : $t('repo.settings.registries.add')"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<RegistryEdit
|
||||
v-else
|
||||
v-model="selectedRegistry"
|
||||
:is-saving="isSaving"
|
||||
@save="createRegistry"
|
||||
@cancel="selectedRegistry = undefined"
|
||||
/>
|
||||
</Settings>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { computed, inject, ref, type Ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import Button from '~/components/atomic/Button.vue';
|
||||
import IconButton from '~/components/atomic/IconButton.vue';
|
||||
import ListItem from '~/components/atomic/ListItem.vue';
|
||||
import InputField from '~/components/form/InputField.vue';
|
||||
import TextField from '~/components/form/TextField.vue';
|
||||
import Settings from '~/components/layout/Settings.vue';
|
||||
import RegistryEdit from '~/components/registry/RegistryEdit.vue';
|
||||
import RegistryList from '~/components/registry/RegistryList.vue';
|
||||
import useApiClient from '~/compositions/useApiClient';
|
||||
import { useAsyncAction } from '~/compositions/useAsyncAction';
|
||||
import useNotifications from '~/compositions/useNotifications';
|
||||
import { usePagination } from '~/compositions/usePaginate';
|
||||
import type { Registry, Repo } from '~/lib/api/types';
|
||||
|
||||
const emptyRegistry: Partial<Registry> = {
|
||||
address: '',
|
||||
username: '',
|
||||
password: '',
|
||||
};
|
||||
|
||||
const apiClient = useApiClient();
|
||||
const notifications = useNotifications();
|
||||
const i18n = useI18n();
|
||||
|
@ -123,9 +82,7 @@ const { doSubmit: createRegistry, isLoading: isSaving } = useAsyncAction(async (
|
|||
await apiClient.createRegistry(repo.value.id, selectedRegistry.value);
|
||||
}
|
||||
notifications.notify({
|
||||
title: isEditingRegistry.value
|
||||
? i18n.t('repo.settings.registries.saved')
|
||||
: i18n.t('repo.settings.registries.created'),
|
||||
title: isEditingRegistry.value ? i18n.t('registries.saved') : i18n.t('registries.created'),
|
||||
type: 'success',
|
||||
});
|
||||
selectedRegistry.value = undefined;
|
||||
|
@ -139,7 +96,15 @@ const { doSubmit: deleteRegistry, isLoading: isDeleting } = useAsyncAction(async
|
|||
|
||||
const registryAddress = encodeURIComponent(_registry.address);
|
||||
await apiClient.deleteRegistry(repo.value.id, registryAddress);
|
||||
notifications.notify({ title: i18n.t('repo.settings.registries.deleted'), type: 'success' });
|
||||
notifications.notify({ title: i18n.t('registries.deleted'), type: 'success' });
|
||||
resetPage();
|
||||
});
|
||||
|
||||
function editRegistry(registry: Registry) {
|
||||
selectedRegistry.value = cloneDeep(registry);
|
||||
}
|
||||
|
||||
function showAddRegistry() {
|
||||
selectedRegistry.value = cloneDeep(emptyRegistry);
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -170,19 +170,53 @@ export default class WoodpeckerClient extends ApiClient {
|
|||
|
||||
getRegistryList(repoId: number, opts?: PaginationOptions): Promise<Registry[] | null> {
|
||||
const query = encodeQueryString(opts);
|
||||
return this._get(`/api/repos/${repoId}/registry?${query}`) as Promise<Registry[] | null>;
|
||||
return this._get(`/api/repos/${repoId}/registries?${query}`) as Promise<Registry[] | null>;
|
||||
}
|
||||
|
||||
createRegistry(repoId: number, registry: Partial<Registry>): Promise<unknown> {
|
||||
return this._post(`/api/repos/${repoId}/registry`, registry);
|
||||
return this._post(`/api/repos/${repoId}/registries`, registry);
|
||||
}
|
||||
|
||||
updateRegistry(repoId: number, registry: Partial<Registry>): Promise<unknown> {
|
||||
return this._patch(`/api/repos/${repoId}/registry/${registry.address}`, registry);
|
||||
return this._patch(`/api/repos/${repoId}/registries/${registry.address}`, registry);
|
||||
}
|
||||
|
||||
deleteRegistry(repoId: number, registryAddress: string): Promise<unknown> {
|
||||
return this._delete(`/api/repos/${repoId}/registry/${registryAddress}`);
|
||||
return this._delete(`/api/repos/${repoId}/registries/${registryAddress}`);
|
||||
}
|
||||
|
||||
getOrgRegistryList(orgId: number, opts?: PaginationOptions): Promise<Registry[] | null> {
|
||||
const query = encodeQueryString(opts);
|
||||
return this._get(`/api/orgs/${orgId}/registries?${query}`) as Promise<Registry[] | null>;
|
||||
}
|
||||
|
||||
createOrgRegistry(orgId: number, registry: Partial<Registry>): Promise<unknown> {
|
||||
return this._post(`/api/orgs/${orgId}/registries`, registry);
|
||||
}
|
||||
|
||||
updateOrgRegistry(orgId: number, registry: Partial<Registry>): Promise<unknown> {
|
||||
return this._patch(`/api/orgs/${orgId}/registries/${registry.address}`, registry);
|
||||
}
|
||||
|
||||
deleteOrgRegistry(orgId: number, registryAddress: string): Promise<unknown> {
|
||||
return this._delete(`/api/orgs/${orgId}/registries/${registryAddress}`);
|
||||
}
|
||||
|
||||
getGlobalRegistryList(opts?: PaginationOptions): Promise<Registry[] | null> {
|
||||
const query = encodeQueryString(opts);
|
||||
return this._get(`/api/registries?${query}`) as Promise<Registry[] | null>;
|
||||
}
|
||||
|
||||
createGlobalRegistry(registry: Partial<Registry>): Promise<unknown> {
|
||||
return this._post(`/api/registries`, registry);
|
||||
}
|
||||
|
||||
updateGlobalRegistry(registry: Partial<Registry>): Promise<unknown> {
|
||||
return this._patch(`/api/registries/${registry.address}`, registry);
|
||||
}
|
||||
|
||||
deleteGlobalRegistry(registryAddress: string): Promise<unknown> {
|
||||
return this._delete(`/api/registries/${registryAddress}`);
|
||||
}
|
||||
|
||||
getCronList(repoId: number, opts?: PaginationOptions): Promise<Cron[] | null> {
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
export interface Registry {
|
||||
id: string;
|
||||
repo_id: number;
|
||||
org_id: number;
|
||||
address: string;
|
||||
username: string;
|
||||
password: string;
|
||||
readonly: boolean;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
<Tab id="secrets" :title="$t('secrets.secrets')">
|
||||
<AdminSecretsTab />
|
||||
</Tab>
|
||||
<Tab id="registries" :title="$t('registries.registries')">
|
||||
<AdminRegistriesTab />
|
||||
</Tab>
|
||||
<Tab id="repos" :title="$t('admin.settings.repos.repos')">
|
||||
<AdminReposTab />
|
||||
</Tab>
|
||||
|
@ -36,6 +39,7 @@ import AdminAgentsTab from '~/components/admin/settings/AdminAgentsTab.vue';
|
|||
import AdminInfoTab from '~/components/admin/settings/AdminInfoTab.vue';
|
||||
import AdminOrgsTab from '~/components/admin/settings/AdminOrgsTab.vue';
|
||||
import AdminQueueTab from '~/components/admin/settings/AdminQueueTab.vue';
|
||||
import AdminRegistriesTab from '~/components/admin/settings/AdminRegistriesTab.vue';
|
||||
import AdminReposTab from '~/components/admin/settings/AdminReposTab.vue';
|
||||
import AdminSecretsTab from '~/components/admin/settings/AdminSecretsTab.vue';
|
||||
import AdminUsersTab from '~/components/admin/settings/AdminUsersTab.vue';
|
||||
|
|
|
@ -14,6 +14,10 @@
|
|||
<Tab id="secrets" :title="$t('secrets.secrets')">
|
||||
<OrgSecretsTab />
|
||||
</Tab>
|
||||
|
||||
<Tab id="registries" :title="$t('registries.registries')">
|
||||
<OrgRegistriesTab />
|
||||
</Tab>
|
||||
</Scaffold>
|
||||
</template>
|
||||
|
||||
|
@ -23,6 +27,7 @@ import { useI18n } from 'vue-i18n';
|
|||
import { useRouter } from 'vue-router';
|
||||
|
||||
import Tab from '~/components/layout/scaffold/Tab.vue';
|
||||
import OrgRegistriesTab from '~/components/org/settings/OrgRegistriesTab.vue';
|
||||
import OrgSecretsTab from '~/components/org/settings/OrgSecretsTab.vue';
|
||||
import { inject } from '~/compositions/useInjectProvide';
|
||||
import useNotifications from '~/compositions/useNotifications';
|
||||
|
|
|
@ -2,15 +2,15 @@
|
|||
<Scaffold enable-tabs :go-back="goBack">
|
||||
<template #title>
|
||||
<span>
|
||||
<router-link :to="{ name: 'org', params: { orgId: repo!.org_id } }" class="hover:underline">
|
||||
{{ repo!.owner }}
|
||||
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'org', params: { orgId: repo!.org_id } }" class="hover:underline">{{
|
||||
repo!.owner
|
||||
/* eslint-disable-next-line @intlify/vue-i18n/no-raw-text */
|
||||
}}</router-link>
|
||||
/
|
||||
<router-link :to="{ name: 'repo' }" class="hover:underline">
|
||||
{{ repo!.name }}
|
||||
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'repo' }" class="hover:underline">{{
|
||||
repo!.name
|
||||
/* eslint-disable-next-line @intlify/vue-i18n/no-raw-text */
|
||||
}}</router-link>
|
||||
/
|
||||
{{ $t('settings') }}
|
||||
</span>
|
||||
|
@ -22,7 +22,7 @@
|
|||
<Tab id="secrets" :title="$t('secrets.secrets')">
|
||||
<SecretsTab />
|
||||
</Tab>
|
||||
<Tab id="registries" :title="$t('repo.settings.registries.registries')">
|
||||
<Tab id="registries" :title="$t('registries.registries')">
|
||||
<RegistriesTab />
|
||||
</Tab>
|
||||
<Tab id="crons" :title="$t('repo.settings.crons.crons')">
|
||||
|
|
|
@ -9,8 +9,10 @@
|
|||
<span class="flex">
|
||||
<router-link :to="{ name: 'org', params: { orgId: repo.org_id } }" class="hover:underline">{{
|
||||
repo.owner
|
||||
/* eslint-disable-next-line @intlify/vue-i18n/no-raw-text */
|
||||
}}</router-link>
|
||||
{{ ` / ${repo.name}` }}
|
||||
/
|
||||
{{ repo.name }}
|
||||
</span>
|
||||
</template>
|
||||
<template #titleActions>
|
||||
|
|
|
@ -10,14 +10,12 @@
|
|||
>
|
||||
<template #title>
|
||||
<span>
|
||||
<router-link :to="{ name: 'org', params: { orgId: repo.org_id } }" class="hover:underline">
|
||||
{{ repo.owner }}
|
||||
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'org', params: { orgId: repo.org_id } }" class="hover:underline">{{
|
||||
repo.owner
|
||||
/* eslint-disable-next-line @intlify/vue-i18n/no-raw-text */
|
||||
}}</router-link>
|
||||
/
|
||||
<router-link :to="{ name: 'repo' }" class="hover:underline">
|
||||
{{ repo.name }}
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'repo' }" class="hover:underline">{{ repo.name }}</router-link>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
|
|
46
woodpecker-go/woodpecker/global_registry.go
Normal file
46
woodpecker-go/woodpecker/global_registry.go
Normal file
|
@ -0,0 +1,46 @@
|
|||
package woodpecker
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
pathGlobalRegistries = "%s/api/registries"
|
||||
pathGlobalRegistry = "%s/api/registries/%s"
|
||||
)
|
||||
|
||||
// GlobalRegistry returns an global registry by name.
|
||||
func (c *client) GlobalRegistry(registry string) (*Registry, error) {
|
||||
out := new(Registry)
|
||||
uri := fmt.Sprintf(pathGlobalRegistry, c.addr, registry)
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// GlobalRegistryList returns a list of all global registries.
|
||||
func (c *client) GlobalRegistryList() ([]*Registry, error) {
|
||||
var out []*Registry
|
||||
uri := fmt.Sprintf(pathGlobalRegistries, c.addr)
|
||||
err := c.get(uri, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// GlobalRegistryCreate creates a global registry.
|
||||
func (c *client) GlobalRegistryCreate(in *Registry) (*Registry, error) {
|
||||
out := new(Registry)
|
||||
uri := fmt.Sprintf(pathGlobalRegistries, c.addr)
|
||||
err := c.post(uri, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// GlobalRegistryUpdate updates a global registry.
|
||||
func (c *client) GlobalRegistryUpdate(in *Registry) (*Registry, error) {
|
||||
out := new(Registry)
|
||||
uri := fmt.Sprintf(pathGlobalRegistry, c.addr, in.Address)
|
||||
err := c.patch(uri, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// GlobalRegistryDelete deletes a global registry.
|
||||
func (c *client) GlobalRegistryDelete(registry string) error {
|
||||
uri := fmt.Sprintf(pathGlobalRegistry, c.addr, registry)
|
||||
return c.delete(uri)
|
||||
}
|
|
@ -138,6 +138,36 @@ type Client interface {
|
|||
// RegistryDelete deletes a registry.
|
||||
RegistryDelete(repoID int64, hostname string) error
|
||||
|
||||
// OrgRegistry returns an organization registry by address.
|
||||
OrgRegistry(orgID int64, registry string) (*Registry, error)
|
||||
|
||||
// OrgRegistryList returns a list of all organization registries.
|
||||
OrgRegistryList(orgID int64) ([]*Registry, error)
|
||||
|
||||
// OrgRegistryCreate creates an organization registry.
|
||||
OrgRegistryCreate(orgID int64, registry *Registry) (*Registry, error)
|
||||
|
||||
// OrgRegistryUpdate updates an organization registry.
|
||||
OrgRegistryUpdate(orgID int64, registry *Registry) (*Registry, error)
|
||||
|
||||
// OrgRegistryDelete deletes an organization registry.
|
||||
OrgRegistryDelete(orgID int64, registry string) error
|
||||
|
||||
// GlobalRegistry returns an global registry by address.
|
||||
GlobalRegistry(registry string) (*Registry, error)
|
||||
|
||||
// GlobalRegistryList returns a list of all global registries.
|
||||
GlobalRegistryList() ([]*Registry, error)
|
||||
|
||||
// GlobalRegistryCreate creates a global registry.
|
||||
GlobalRegistryCreate(registry *Registry) (*Registry, error)
|
||||
|
||||
// GlobalRegistryUpdate updates a global registry.
|
||||
GlobalRegistryUpdate(registry *Registry) (*Registry, error)
|
||||
|
||||
// GlobalRegistryDelete deletes a global registry.
|
||||
GlobalRegistryDelete(registry string) error
|
||||
|
||||
// Secret returns a secret by name.
|
||||
Secret(repoID int64, secret string) (*Secret, error)
|
||||
|
||||
|
|
|
@ -353,6 +353,144 @@ func (_m *Client) Deploy(repoID int64, pipeline int64, env string, params map[st
|
|||
return r0, r1
|
||||
}
|
||||
|
||||
// GlobalRegistry provides a mock function with given fields: registry
|
||||
func (_m *Client) GlobalRegistry(registry string) (*woodpecker.Registry, error) {
|
||||
ret := _m.Called(registry)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GlobalRegistry")
|
||||
}
|
||||
|
||||
var r0 *woodpecker.Registry
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string) (*woodpecker.Registry, error)); ok {
|
||||
return rf(registry)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string) *woodpecker.Registry); ok {
|
||||
r0 = rf(registry)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*woodpecker.Registry)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(registry)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GlobalRegistryCreate provides a mock function with given fields: registry
|
||||
func (_m *Client) GlobalRegistryCreate(registry *woodpecker.Registry) (*woodpecker.Registry, error) {
|
||||
ret := _m.Called(registry)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GlobalRegistryCreate")
|
||||
}
|
||||
|
||||
var r0 *woodpecker.Registry
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(*woodpecker.Registry) (*woodpecker.Registry, error)); ok {
|
||||
return rf(registry)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(*woodpecker.Registry) *woodpecker.Registry); ok {
|
||||
r0 = rf(registry)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*woodpecker.Registry)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(*woodpecker.Registry) error); ok {
|
||||
r1 = rf(registry)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GlobalRegistryDelete provides a mock function with given fields: registry
|
||||
func (_m *Client) GlobalRegistryDelete(registry string) error {
|
||||
ret := _m.Called(registry)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GlobalRegistryDelete")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(registry)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// GlobalRegistryList provides a mock function with given fields:
|
||||
func (_m *Client) GlobalRegistryList() ([]*woodpecker.Registry, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GlobalRegistryList")
|
||||
}
|
||||
|
||||
var r0 []*woodpecker.Registry
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func() ([]*woodpecker.Registry, error)); ok {
|
||||
return rf()
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func() []*woodpecker.Registry); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*woodpecker.Registry)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GlobalRegistryUpdate provides a mock function with given fields: registry
|
||||
func (_m *Client) GlobalRegistryUpdate(registry *woodpecker.Registry) (*woodpecker.Registry, error) {
|
||||
ret := _m.Called(registry)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GlobalRegistryUpdate")
|
||||
}
|
||||
|
||||
var r0 *woodpecker.Registry
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(*woodpecker.Registry) (*woodpecker.Registry, error)); ok {
|
||||
return rf(registry)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(*woodpecker.Registry) *woodpecker.Registry); ok {
|
||||
r0 = rf(registry)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*woodpecker.Registry)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(*woodpecker.Registry) error); ok {
|
||||
r1 = rf(registry)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GlobalSecret provides a mock function with given fields: secret
|
||||
func (_m *Client) GlobalSecret(secret string) (*woodpecker.Secret, error) {
|
||||
ret := _m.Called(secret)
|
||||
|
@ -599,6 +737,144 @@ func (_m *Client) OrgLookup(orgName string) (*woodpecker.Org, error) {
|
|||
return r0, r1
|
||||
}
|
||||
|
||||
// OrgRegistry provides a mock function with given fields: orgID, registry
|
||||
func (_m *Client) OrgRegistry(orgID int64, registry string) (*woodpecker.Registry, error) {
|
||||
ret := _m.Called(orgID, registry)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for OrgRegistry")
|
||||
}
|
||||
|
||||
var r0 *woodpecker.Registry
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(int64, string) (*woodpecker.Registry, error)); ok {
|
||||
return rf(orgID, registry)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(int64, string) *woodpecker.Registry); ok {
|
||||
r0 = rf(orgID, registry)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*woodpecker.Registry)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(int64, string) error); ok {
|
||||
r1 = rf(orgID, registry)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// OrgRegistryCreate provides a mock function with given fields: orgID, registry
|
||||
func (_m *Client) OrgRegistryCreate(orgID int64, registry *woodpecker.Registry) (*woodpecker.Registry, error) {
|
||||
ret := _m.Called(orgID, registry)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for OrgRegistryCreate")
|
||||
}
|
||||
|
||||
var r0 *woodpecker.Registry
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(int64, *woodpecker.Registry) (*woodpecker.Registry, error)); ok {
|
||||
return rf(orgID, registry)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(int64, *woodpecker.Registry) *woodpecker.Registry); ok {
|
||||
r0 = rf(orgID, registry)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*woodpecker.Registry)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(int64, *woodpecker.Registry) error); ok {
|
||||
r1 = rf(orgID, registry)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// OrgRegistryDelete provides a mock function with given fields: orgID, registry
|
||||
func (_m *Client) OrgRegistryDelete(orgID int64, registry string) error {
|
||||
ret := _m.Called(orgID, registry)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for OrgRegistryDelete")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(int64, string) error); ok {
|
||||
r0 = rf(orgID, registry)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// OrgRegistryList provides a mock function with given fields: orgID
|
||||
func (_m *Client) OrgRegistryList(orgID int64) ([]*woodpecker.Registry, error) {
|
||||
ret := _m.Called(orgID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for OrgRegistryList")
|
||||
}
|
||||
|
||||
var r0 []*woodpecker.Registry
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(int64) ([]*woodpecker.Registry, error)); ok {
|
||||
return rf(orgID)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(int64) []*woodpecker.Registry); ok {
|
||||
r0 = rf(orgID)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*woodpecker.Registry)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(int64) error); ok {
|
||||
r1 = rf(orgID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// OrgRegistryUpdate provides a mock function with given fields: orgID, registry
|
||||
func (_m *Client) OrgRegistryUpdate(orgID int64, registry *woodpecker.Registry) (*woodpecker.Registry, error) {
|
||||
ret := _m.Called(orgID, registry)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for OrgRegistryUpdate")
|
||||
}
|
||||
|
||||
var r0 *woodpecker.Registry
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(int64, *woodpecker.Registry) (*woodpecker.Registry, error)); ok {
|
||||
return rf(orgID, registry)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(int64, *woodpecker.Registry) *woodpecker.Registry); ok {
|
||||
r0 = rf(orgID, registry)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*woodpecker.Registry)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(int64, *woodpecker.Registry) error); ok {
|
||||
r1 = rf(orgID, registry)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// OrgSecret provides a mock function with given fields: orgID, secret
|
||||
func (_m *Client) OrgSecret(orgID int64, secret string) (*woodpecker.Secret, error) {
|
||||
ret := _m.Called(orgID, secret)
|
||||
|
|
|
@ -3,10 +3,12 @@ package woodpecker
|
|||
import "fmt"
|
||||
|
||||
const (
|
||||
pathOrg = "%s/api/orgs/%d"
|
||||
pathOrgLookup = "%s/api/orgs/lookup/%s"
|
||||
pathOrgSecrets = "%s/api/orgs/%d/secrets"
|
||||
pathOrgSecret = "%s/api/orgs/%d/secrets/%s"
|
||||
pathOrg = "%s/api/orgs/%d"
|
||||
pathOrgLookup = "%s/api/orgs/lookup/%s"
|
||||
pathOrgSecrets = "%s/api/orgs/%d/secrets"
|
||||
pathOrgSecret = "%s/api/orgs/%d/secrets/%s"
|
||||
pathOrgRegistries = "%s/api/orgs/%d/registries"
|
||||
pathOrgRegistry = "%s/api/orgs/%d/registries/%s"
|
||||
)
|
||||
|
||||
// Org returns an organization by id.
|
||||
|
@ -62,3 +64,41 @@ func (c *client) OrgSecretDelete(orgID int64, secret string) error {
|
|||
uri := fmt.Sprintf(pathOrgSecret, c.addr, orgID, secret)
|
||||
return c.delete(uri)
|
||||
}
|
||||
|
||||
// OrgRegistry returns an organization registry by address.
|
||||
func (c *client) OrgRegistry(orgID int64, registry string) (*Registry, error) {
|
||||
out := new(Registry)
|
||||
uri := fmt.Sprintf(pathOrgRegistry, c.addr, orgID, registry)
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// OrgRegistryList returns a list of all organization registries.
|
||||
func (c *client) OrgRegistryList(orgID int64) ([]*Registry, error) {
|
||||
var out []*Registry
|
||||
uri := fmt.Sprintf(pathOrgRegistries, c.addr, orgID)
|
||||
err := c.get(uri, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// OrgRegistryCreate creates an organization registry.
|
||||
func (c *client) OrgRegistryCreate(orgID int64, in *Registry) (*Registry, error) {
|
||||
out := new(Registry)
|
||||
uri := fmt.Sprintf(pathOrgRegistries, c.addr, orgID)
|
||||
err := c.post(uri, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// OrgRegistryUpdate updates an organization registry.
|
||||
func (c *client) OrgRegistryUpdate(orgID int64, in *Registry) (*Registry, error) {
|
||||
out := new(Registry)
|
||||
uri := fmt.Sprintf(pathOrgRegistry, c.addr, orgID, in.Address)
|
||||
err := c.patch(uri, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// OrgRegistryDelete deletes an organization registry.
|
||||
func (c *client) OrgRegistryDelete(orgID int64, registry string) error {
|
||||
uri := fmt.Sprintf(pathOrgRegistry, c.addr, orgID, registry)
|
||||
return c.delete(uri)
|
||||
}
|
||||
|
|
|
@ -18,8 +18,8 @@ const (
|
|||
pathStop = "%s/api/repos/%d/pipelines/%d/cancel"
|
||||
pathRepoSecrets = "%s/api/repos/%d/secrets"
|
||||
pathRepoSecret = "%s/api/repos/%d/secrets/%s"
|
||||
pathRepoRegistries = "%s/api/repos/%d/registry"
|
||||
pathRepoRegistry = "%s/api/repos/%d/registry/%s"
|
||||
pathRepoRegistries = "%s/api/repos/%d/registries"
|
||||
pathRepoRegistry = "%s/api/repos/%d/registries/%s"
|
||||
pathRepoCrons = "%s/api/repos/%d/cron"
|
||||
pathRepoCron = "%s/api/repos/%d/cron/%d"
|
||||
)
|
||||
|
|
|
@ -132,6 +132,8 @@ type (
|
|||
// Registry represents a docker registry with credentials.
|
||||
Registry struct {
|
||||
ID int64 `json:"id"`
|
||||
OrgID int64 `json:"org_id"`
|
||||
RepoID int64 `json:"repo_id"`
|
||||
Address string `json:"address"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password,omitempty"`
|
||||
|
|
Loading…
Reference in a new issue