Skip to content

Commit

Permalink
add -check-names to perform a deeper validation
Browse files Browse the repository at this point in the history
  • Loading branch information
xrstf committed Jun 1, 2021
1 parent 6ff5eb5 commit 696a33c
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 26 deletions.
25 changes: 20 additions & 5 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func main() {
showVersion = false
confirm = false
validate = false
checkNames = false
exportMode = false
createRepositories = false
deleteRepositories = false
Expand All @@ -36,7 +37,8 @@ func main() {
flag.StringVar(&configFile, "config", configFile, "path to the config.yaml")
flag.BoolVar(&showVersion, "version", showVersion, "show the Aquayman version and exit")
flag.BoolVar(&confirm, "confirm", confirm, "must be set to actually perform any changes on quay.io")
flag.BoolVar(&validate, "validate", validate, "validate the given configuration and then exit")
flag.BoolVar(&validate, "validate", validate, "validate the given configuration syntax and then exit")
flag.BoolVar(&checkNames, "check-names", checkNames, "(only with -validate) validate that users actually exist (requires valid quay.io credentials)")
flag.BoolVar(&exportMode, "export", exportMode, "export quay.io state and update the config file (-config flag)")
flag.BoolVar(&createRepositories, "create-repos", createRepositories, "create repositories listed in the config file but not existing on quay.io yet")
flag.BoolVar(&deleteRepositories, "delete-repos", deleteRepositories, "delete repositories on quay.io that are not listed in the config file")
Expand All @@ -58,10 +60,21 @@ func main() {
log.Fatalf("⚠ Failed to load config %q: %v.", configFile, err)
}

var (
client *quay.Client
)

// validate config unless in export mode, where an incomplete
// configuration is allowed and even expected
if !exportMode {
if err := cfg.Validate(); err != nil {
if checkNames {
client, err = quay.NewClient(getToken(), 30*time.Second, true)
if err != nil {
log.Fatalf("⚠ Failed to create quay.io API client: %v.", err)
}
}

if err := cfg.Validate(ctx, client); err != nil {
log.Fatalf("Configuration is invalid: %v", err)
}
}
Expand All @@ -71,9 +84,11 @@ func main() {
return
}

client, err := quay.NewClient(getToken(), 30*time.Second, !confirm)
if err != nil {
log.Fatalf("⚠ Failed to create quay.io API client: %v.", err)
if client == nil {
client, err = quay.NewClient(getToken(), 30*time.Second, !confirm)
if err != nil {
log.Fatalf("⚠ Failed to create quay.io API client: %v.", err)
}
}

if exportMode {
Expand Down
67 changes: 46 additions & 21 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package config

import (
"context"
"errors"
"fmt"
"os"
Expand Down Expand Up @@ -98,7 +99,7 @@ func validRepositoryRole(role quay.RepositoryRole) bool {
return false
}

func (c *Config) Validate() error {
func (c *Config) Validate(ctx context.Context, client *quay.Client) error {
if c.Organization == "" {
return errors.New("no organization configured")
}
Expand All @@ -115,6 +116,36 @@ func (c *Config) Validate() error {
}

teamNames = append(teamNames, team.Name)

if client != nil {
for _, member := range team.Members {
if _, err := client.GetUser(ctx, member); err != nil {
return fmt.Errorf("user %q in team %q does not exist: %v", member, team.Name, err)
}
}
}
}

robotNames := []string{}
robotPattern := regexp.MustCompile(`^[a-z][a-z0-9_]{1,254}$`)
prefix := c.Organization + "+"

for _, robot := range c.Robots {
fullName := fmt.Sprintf("%s+%s", c.Organization, robot.Name)

if util.StringSliceContains(robotNames, fullName) {
return fmt.Errorf("duplicate robot %q defined", robot.Name)
}

if strings.HasPrefix(robot.Name, prefix) {
return fmt.Errorf("robot %q must be given as a short name, without the organization prefix (must be \"%s\")", robot.Name, strings.TrimPrefix(robot.Name, prefix))
}

if !robotPattern.MatchString(robot.Name) {
return fmt.Errorf("robot %q has an invalid name, must be alphanumeric lowercase", robot.Name)
}

robotNames = append(robotNames, fullName)
}

repoNames := []string{}
Expand All @@ -133,6 +164,10 @@ func (c *Config) Validate() error {
}

for teamName, roleName := range repo.Teams {
if !util.StringSliceContains(teamNames, teamName) {
return fmt.Errorf("invalid team %q assigned to repo %q: team does not exist", teamName, repo.Name)
}

if !validRepositoryRole(roleName) {
return fmt.Errorf("role for team %s in repo %q is invalid (%q), must be one of %v", teamName, repo.Name, roleName, quay.AllRepositoryRoles)
}
Expand All @@ -142,29 +177,19 @@ func (c *Config) Validate() error {
if !validRepositoryRole(roleName) {
return fmt.Errorf("role for user %s in repo %q is invalid (%q), must be one of %v", userName, repo.Name, roleName, quay.AllRepositoryRoles)
}
}

repoNames = append(repoNames, repo.Name)
}

robotNames := []string{}
robotPattern := regexp.MustCompile(`^[a-z][a-z0-9_]{1,254}$`)
prefix := c.Organization + "+"

for _, robot := range c.Robots {
if util.StringSliceContains(robotNames, robot.Name) {
return fmt.Errorf("duplicate robot %q defined", robot.Name)
}

if strings.HasPrefix(robot.Name, prefix) {
return fmt.Errorf("robot %q must be given as a short name, without the organization prefix (must be \"%s\")", robot.Name, strings.TrimPrefix(robot.Name, prefix))
}

if !robotPattern.MatchString(robot.Name) {
return fmt.Errorf("robot %q has an invalid name, must be alphanumeric lowercase", robot.Name)
if quay.IsRobotUsername(userName) {
if !util.StringSliceContains(robotNames, userName) {
return fmt.Errorf("invalid robot %q assigned to repo %q: robot does not exist", userName, repo.Name)
}
} else if client != nil {
if _, err := client.GetUser(ctx, userName); err != nil {
return fmt.Errorf("invalid user %q assigned to repo %q: user does not exist", userName, repo.Name)
}
}
}

robotNames = append(robotNames, robot.Name)
repoNames = append(repoNames, repo.Name)
}

return nil
Expand Down
4 changes: 4 additions & 0 deletions pkg/quay/robot.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import (
"strings"
)

func IsRobotUsername(name string) bool {
return strings.Contains(name, "+")
}

type Robot struct {
Name string `json:"name"`
Description string `json:"description"`
Expand Down
19 changes: 19 additions & 0 deletions pkg/quay/user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package quay

import (
"context"
"fmt"
"net/url"
)

type User struct {
Username string `json:"username"`
}

func (c *Client) GetUser(ctx context.Context, username string) (*User, error) {
response := User{}
path := fmt.Sprintf("/users/%s", url.PathEscape(username))
err := c.call(ctx, "GET", path, nil, nil, &response)

return &response, err
}

0 comments on commit 696a33c

Please sign in to comment.