feat: add tags

This commit is contained in:
Kevin Franklin Kim 2023-09-21 13:25:18 +02:00
parent c0c52b85d9
commit bede368045
No known key found for this signature in database
25 changed files with 198 additions and 31 deletions

View File

@ -5,6 +5,7 @@ squadron:
checkout: checkout:
backend: backend:
chart: <% env "PROJECT_ROOT" %>/../common/charts/backend chart: <% env "PROJECT_ROOT" %>/../common/charts/backend
tags: [ "backend" ]
builds: builds:
default: default:
tag: <% .Global.docker.tag | quote %> tag: <% .Global.docker.tag | quote %>

View File

@ -5,6 +5,7 @@ squadron:
checkout: checkout:
frontend: frontend:
chart: <% env "PROJECT_ROOT" %>/../common/charts/frontend chart: <% env "PROJECT_ROOT" %>/../common/charts/frontend
tags: [ "frontend" ]
builds: builds:
default: default:
tag: "<% .Global.docker.tag %>" tag: "<% .Global.docker.tag %>"

View File

@ -5,6 +5,7 @@ squadron:
storefinder: storefinder:
backend: backend:
chart: <% env "PROJECT_ROOT" %>/../common/charts/backend chart: <% env "PROJECT_ROOT" %>/../common/charts/backend
tags: [ "backend" ]
builds: builds:
default: default:
tag: <% .Global.docker.tag | quote %> tag: <% .Global.docker.tag | quote %>

View File

@ -5,6 +5,7 @@ squadron:
storefinder: storefinder:
frontend: frontend:
chart: <% env "PROJECT_ROOT" %>/../common/charts/frontend chart: <% env "PROJECT_ROOT" %>/../common/charts/frontend
tags: [ "frontend" ]
builds: builds:
default: default:
tag: "<% .Global.docker.tag %>" tag: "<% .Global.docker.tag %>"

View File

@ -8,6 +8,7 @@ import (
func init() { func init() {
buildCmd.Flags().BoolVarP(&flagPush, "push", "p", false, "pushes built squadron units to the registry") buildCmd.Flags().BoolVarP(&flagPush, "push", "p", false, "pushes built squadron units to the registry")
buildCmd.Flags().IntVar(&flagParallel, "parallel", 1, "run command in parallel") buildCmd.Flags().IntVar(&flagParallel, "parallel", 1, "run command in parallel")
buildCmd.Flags().StringSliceVar(&flagTags, "tags", nil, "list of tags to include or exclude (can specify multiple or separate values with commas: tag1,tag2,-tag3)")
buildCmd.Flags().StringSliceVar(&flagBuildArgs, "build-args", nil, "additional docker buildx build args") buildCmd.Flags().StringSliceVar(&flagBuildArgs, "build-args", nil, "additional docker buildx build args")
buildCmd.Flags().StringSliceVar(&flagPushArgs, "push-args", nil, "additional docker push args") buildCmd.Flags().StringSliceVar(&flagPushArgs, "push-args", nil, "additional docker push args")
} }
@ -25,7 +26,7 @@ var buildCmd = &cobra.Command{
} }
squadronName, unitNames := parseSquadronAndUnitNames(args) squadronName, unitNames := parseSquadronAndUnitNames(args)
if err := sq.FilterConfig(squadronName, unitNames); err != nil { if err := sq.FilterConfig(squadronName, unitNames, flagTags); err != nil {
return err return err
} }

View File

@ -3,14 +3,14 @@ package actions
import ( import (
"fmt" "fmt"
"github.com/foomo/squadron"
"github.com/foomo/squadron/internal/util" "github.com/foomo/squadron/internal/util"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/foomo/squadron"
) )
func init() { func init() {
configCmd.Flags().BoolVar(&flagNoRender, "no-render", false, "don't render the config template") configCmd.Flags().BoolVar(&flagNoRender, "no-render", false, "don't render the config template")
configCmd.Flags().StringSliceVar(&flagTags, "tags", nil, "list of tags to include or exclude (can specify multiple or separate values with commas: tag1,tag2,-tag3)")
} }
var configCmd = &cobra.Command{ var configCmd = &cobra.Command{
@ -26,7 +26,7 @@ var configCmd = &cobra.Command{
} }
squadronName, unitNames := parseSquadronAndUnitNames(args) squadronName, unitNames := parseSquadronAndUnitNames(args)
if err := sq.FilterConfig(squadronName, unitNames); err != nil { if err := sq.FilterConfig(squadronName, unitNames, flagTags); err != nil {
return err return err
} }

View File

@ -7,7 +7,7 @@ import (
) )
func init() { func init() {
diffCmd.Flags().StringVarP(&flagNamespace, "namespace", "n", "default", "specifies the namespace") diffCmd.Flags().StringVarP(&flagNamespace, "namespace", "n", "default", "set the namespace name or template (default, squadron-{{.Squadron}}-{{.Unit}})")
diffCmd.Flags().IntVar(&flagParallel, "parallel", 1, "run command in parallel") diffCmd.Flags().IntVar(&flagParallel, "parallel", 1, "run command in parallel")
} }

View File

@ -8,7 +8,8 @@ import (
func init() { func init() {
downCmd.Flags().IntVar(&flagParallel, "parallel", 1, "run command in parallel") downCmd.Flags().IntVar(&flagParallel, "parallel", 1, "run command in parallel")
downCmd.Flags().StringVarP(&flagNamespace, "namespace", "n", "default", "Specifies the namespace") downCmd.Flags().StringVarP(&flagNamespace, "namespace", "n", "default", "set the namespace name or template (default, squadron-{{.Squadron}}-{{.Unit}})")
downCmd.Flags().StringSliceVar(&flagTags, "tags", nil, "list of tags to include or exclude (can specify multiple or separate values with commas: tag1,tag2,-tag3)")
} }
var downCmd = &cobra.Command{ var downCmd = &cobra.Command{
@ -26,7 +27,7 @@ var downCmd = &cobra.Command{
args, helmArgs := parseExtraArgs(args) args, helmArgs := parseExtraArgs(args)
squadronName, unitNames := parseSquadronAndUnitNames(args) squadronName, unitNames := parseSquadronAndUnitNames(args)
if err := sq.FilterConfig(squadronName, unitNames); err != nil { if err := sq.FilterConfig(squadronName, unitNames, flagTags); err != nil {
return err return err
} }

View File

@ -10,6 +10,10 @@ import (
var flagPrefixSquadron bool var flagPrefixSquadron bool
func init() {
listCmd.Flags().StringSliceVar(&flagTags, "tags", nil, "list of tags to include or exclude (can specify multiple or separate values with commas: tag1,tag2,-tag3)")
}
var listCmd = &cobra.Command{ var listCmd = &cobra.Command{
Use: "list [SQUADRON]", Use: "list [SQUADRON]",
Short: "list squadron units", Short: "list squadron units",
@ -23,7 +27,7 @@ var listCmd = &cobra.Command{
} }
squadronName, unitNames := parseSquadronAndUnitNames(args) squadronName, unitNames := parseSquadronAndUnitNames(args)
if err := sq.FilterConfig(squadronName, unitNames); err != nil { if err := sq.FilterConfig(squadronName, unitNames, flagTags); err != nil {
return err return err
} }

View File

@ -6,11 +6,12 @@ import (
) )
func init() { func init() {
pushCmd.Flags().StringVarP(&flagNamespace, "namespace", "n", "default", "specifies the namespace") pushCmd.Flags().StringVarP(&flagNamespace, "namespace", "n", "default", "set the namespace name or template (default, squadron-{{.Squadron}}-{{.Unit}})")
pushCmd.Flags().BoolVarP(&flagBuild, "build", "b", false, "builds or rebuilds units") pushCmd.Flags().BoolVarP(&flagBuild, "build", "b", false, "builds or rebuilds units")
pushCmd.Flags().IntVar(&flagParallel, "parallel", 1, "run command in parallel") pushCmd.Flags().IntVar(&flagParallel, "parallel", 1, "run command in parallel")
pushCmd.Flags().StringSliceVar(&flagBuildArgs, "build-args", nil, "additional docker buildx build args") pushCmd.Flags().StringSliceVar(&flagBuildArgs, "build-args", nil, "additional docker buildx build args")
pushCmd.Flags().StringSliceVar(&flagPushArgs, "push-args", nil, "additional docker push args") pushCmd.Flags().StringSliceVar(&flagPushArgs, "push-args", nil, "additional docker push args")
pushCmd.Flags().StringSliceVar(&flagTags, "tags", nil, "list of tags to include or exclude (can specify multiple or separate values with commas: tag1,tag2,-tag3)")
} }
var pushCmd = &cobra.Command{ var pushCmd = &cobra.Command{
@ -25,7 +26,7 @@ var pushCmd = &cobra.Command{
} }
squadronName, unitNames := parseSquadronAndUnitNames(args) squadronName, unitNames := parseSquadronAndUnitNames(args)
if err := sq.FilterConfig(squadronName, unitNames); err != nil { if err := sq.FilterConfig(squadronName, unitNames, flagTags); err != nil {
return err return err
} }

View File

@ -8,8 +8,9 @@ import (
func init() { func init() {
rollbackCmd.Flags().IntVar(&flagParallel, "parallel", 1, "run command in parallel") rollbackCmd.Flags().IntVar(&flagParallel, "parallel", 1, "run command in parallel")
rollbackCmd.Flags().StringVarP(&flagNamespace, "namespace", "n", "default", "specifies the namespace") rollbackCmd.Flags().StringVarP(&flagNamespace, "namespace", "n", "default", "set the namespace name or template (default, squadron-{{.Squadron}}-{{.Unit}})")
rollbackCmd.Flags().StringVarP(&flagRevision, "revision", "r", "", "specifies the revision to roll back to") rollbackCmd.Flags().StringVarP(&flagRevision, "revision", "r", "", "specifies the revision to roll back to")
rollbackCmd.Flags().StringSliceVar(&flagTags, "tags", nil, "list of tags to include or exclude (can specify multiple or separate values with commas: tag1,tag2,-tag3)")
} }
var rollbackCmd = &cobra.Command{ var rollbackCmd = &cobra.Command{
@ -26,7 +27,7 @@ var rollbackCmd = &cobra.Command{
args, helmArgs := parseExtraArgs(args) args, helmArgs := parseExtraArgs(args)
squadronName, unitNames := parseSquadronAndUnitNames(args) squadronName, unitNames := parseSquadronAndUnitNames(args)
if err := sq.FilterConfig(squadronName, unitNames); err != nil { if err := sq.FilterConfig(squadronName, unitNames, flagTags); err != nil {
return err return err
} }

View File

@ -46,6 +46,7 @@ var (
flagParallel int flagParallel int
flagBuildArgs []string flagBuildArgs []string
flagPushArgs []string flagPushArgs []string
flagTags []string
flagDiff bool flagDiff bool
flagFiles []string flagFiles []string
) )

View File

@ -8,7 +8,8 @@ import (
func init() { func init() {
statusCmd.Flags().IntVar(&flagParallel, "parallel", 1, "run command in parallel") statusCmd.Flags().IntVar(&flagParallel, "parallel", 1, "run command in parallel")
statusCmd.Flags().StringVarP(&flagNamespace, "namespace", "n", "default", "specifies the namespace") statusCmd.Flags().StringVarP(&flagNamespace, "namespace", "n", "default", "set the namespace name or template (default, squadron-{{.Squadron}}-{{.Unit}})")
statusCmd.Flags().StringSliceVar(&flagTags, "tags", nil, "list of tags to include or exclude (can specify multiple or separate values with commas: tag1,tag2,-tag3)")
} }
var statusCmd = &cobra.Command{ var statusCmd = &cobra.Command{
@ -25,7 +26,7 @@ var statusCmd = &cobra.Command{
args, helmArgs := parseExtraArgs(args) args, helmArgs := parseExtraArgs(args)
squadronName, unitNames := parseSquadronAndUnitNames(args) squadronName, unitNames := parseSquadronAndUnitNames(args)
if err := sq.FilterConfig(squadronName, unitNames); err != nil { if err := sq.FilterConfig(squadronName, unitNames, flagTags); err != nil {
return err return err
} }

View File

@ -9,7 +9,8 @@ import (
) )
func init() { func init() {
templateCmd.Flags().StringVarP(&flagNamespace, "namespace", "n", "default", "specifies the namespace") templateCmd.Flags().StringVarP(&flagNamespace, "namespace", "n", "default", "set the namespace name or template (default, squadron-{{.Squadron}}-{{.Unit}})")
templateCmd.Flags().StringSliceVar(&flagTags, "tags", nil, "list of tags to include or exclude (can specify multiple or separate values with commas: tag1,tag2,-tag3)")
} }
var templateCmd = &cobra.Command{ var templateCmd = &cobra.Command{
@ -27,7 +28,7 @@ var templateCmd = &cobra.Command{
args, helmArgs := parseExtraArgs(args) args, helmArgs := parseExtraArgs(args)
squadronName, unitNames := parseSquadronAndUnitNames(args) squadronName, unitNames := parseSquadronAndUnitNames(args)
if err := sq.FilterConfig(squadronName, unitNames); err != nil { if err := sq.FilterConfig(squadronName, unitNames, flagTags); err != nil {
return err return err
} }

View File

@ -10,12 +10,13 @@ import (
) )
func init() { func init() {
upCmd.Flags().StringVarP(&flagNamespace, "namespace", "n", "default", "specifies the namespace") upCmd.Flags().StringVarP(&flagNamespace, "namespace", "n", "default", "set the namespace name or template (default, squadron-{{.Squadron}}-{{.Unit}})")
upCmd.Flags().BoolVarP(&flagBuild, "build", "b", false, "builds or rebuilds units") upCmd.Flags().BoolVarP(&flagBuild, "build", "b", false, "builds or rebuilds units")
upCmd.Flags().BoolVarP(&flagPush, "push", "p", false, "pushes units to the registry") upCmd.Flags().BoolVarP(&flagPush, "push", "p", false, "pushes units to the registry")
upCmd.Flags().IntVar(&flagParallel, "parallel", 1, "run command in parallel") upCmd.Flags().IntVar(&flagParallel, "parallel", 1, "run command in parallel")
upCmd.Flags().StringSliceVar(&flagBuildArgs, "build-args", nil, "additional docker buildx build args") upCmd.Flags().StringSliceVar(&flagBuildArgs, "build-args", nil, "additional docker buildx build args")
upCmd.Flags().StringSliceVar(&flagPushArgs, "push-args", nil, "additional docker push args") upCmd.Flags().StringSliceVar(&flagPushArgs, "push-args", nil, "additional docker push args")
upCmd.Flags().StringSliceVar(&flagTags, "tags", nil, "list of tags to include or exclude (can specify multiple or separate values with commas: tag1,tag2,-tag3)")
} }
var upCmd = &cobra.Command{ var upCmd = &cobra.Command{
@ -32,7 +33,7 @@ var upCmd = &cobra.Command{
args, helmArgs := parseExtraArgs(args) args, helmArgs := parseExtraArgs(args)
squadronName, unitNames := parseSquadronAndUnitNames(args) squadronName, unitNames := parseSquadronAndUnitNames(args)
if err := sq.FilterConfig(squadronName, unitNames); err != nil { if err := sq.FilterConfig(squadronName, unitNames, flagTags); err != nil {
return err return err
} }

View File

@ -4,10 +4,9 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/foomo/squadron/internal/util"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"github.com/foomo/squadron/internal/util"
) )
type Build struct { type Build struct {

View File

@ -80,6 +80,15 @@ func (m Map[T]) Filter(keys ...string) error {
return nil return nil
} }
func (m Map[T]) FilterFn(handler func(key string, value T) bool) error {
for key, value := range m {
if !handler(key, value) {
delete(m, key)
}
}
return nil
}
func (m Map[T]) Iterate(handler func(key string, value T) error) error { func (m Map[T]) Iterate(handler func(key string, value T) error) error {
if len(m) == 0 { if len(m) == 0 {
return nil return nil

3
internal/config/tag.go Normal file
View File

@ -0,0 +1,3 @@
package config
type Tag string

View File

@ -14,6 +14,7 @@ import (
type Unit struct { type Unit struct {
Chart helm.Dependency `yaml:"chart,omitempty"` Chart helm.Dependency `yaml:"chart,omitempty"`
Tags []Tag `yaml:"tags,omitempty"`
Builds map[string]Build `yaml:"builds,omitempty"` Builds map[string]Build `yaml:"builds,omitempty"`
Values map[string]interface{} `yaml:"values,omitempty"` Values map[string]interface{} `yaml:"values,omitempty"`
} }

View File

@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"slices"
"strings" "strings"
"text/template" "text/template"
@ -102,13 +103,11 @@ func (sq *Squadron) MergeConfigFiles() error {
return nil return nil
} }
func (sq *Squadron) FilterConfig(squadron string, units []string) error { func (sq *Squadron) FilterConfig(squadron string, units, tags []string) error {
if len(squadron) == 0 { if len(squadron) > 0 {
return nil if err := sq.Config().Squadrons.Filter(squadron); err != nil {
} return err
}
if err := sq.Config().Squadrons.Filter(squadron); err != nil {
return err
} }
if len(squadron) > 0 && len(units) > 0 { if len(squadron) > 0 && len(units) > 0 {
@ -117,6 +116,27 @@ func (sq *Squadron) FilterConfig(squadron string, units []string) error {
} }
} }
if len(tags) > 0 {
if err := sq.Config().Squadrons.Iterate(func(key string, value config.Map[*config.Unit]) error {
return value.FilterFn(func(k string, v *config.Unit) bool {
for _, tag := range tags {
if strings.HasPrefix(tag, "-") {
if slices.Contains(v.Tags, config.Tag(strings.TrimPrefix(tag, "-"))) {
return false
}
} else if !slices.Contains(v.Tags, config.Tag(tag)) {
return false
}
}
return true
})
}); err != nil {
return err
}
}
sq.c.Trim()
value, err := yamlv2.Marshal(sq.c) value, err := yamlv2.Marshal(sq.c)
if err != nil { if err != nil {
return err return err

View File

@ -9,15 +9,20 @@ import (
"github.com/foomo/squadron" "github.com/foomo/squadron"
"github.com/foomo/squadron/internal/testutils" "github.com/foomo/squadron/internal/testutils"
"github.com/foomo/squadron/internal/util" "github.com/foomo/squadron/internal/util"
"github.com/pterm/pterm"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestConfigSimpleSnapshot(t *testing.T) { func TestConfigSimpleSnapshot(t *testing.T) {
pterm.EnableDebugMessages()
require.NoError(t, os.Setenv("PROJECT_ROOT", "."))
tests := []struct { tests := []struct {
name string name string
files []string files []string
squadron string squadron string
units []string units []string
tags []string
}{ }{
{ {
name: "blank", name: "blank",
@ -39,21 +44,25 @@ func TestConfigSimpleSnapshot(t *testing.T) {
name: "template", name: "template",
files: []string{"squadron.yaml"}, files: []string{"squadron.yaml"},
}, },
{
name: "tags",
tags: []string{"backend", "-skip"},
files: []string{"squadron.yaml"},
},
} }
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(tt *testing.T) { t.Run(test.name, func(tt *testing.T) {
config(tt, test.name, test.files, test.squadron, test.units) config(tt, test.name, test.files, test.squadron, test.units, test.tags)
}) })
} }
} }
func config(t *testing.T, name string, files []string, squadronName string, unitNames []string) { func config(t *testing.T, name string, files []string, squadronName string, unitNames, tags []string) {
t.Helper() t.Helper()
var cwd string var cwd string
ctx := context.TODO() ctx := context.TODO()
require.NoError(t, util.ValidatePath(".", &cwd)) require.NoError(t, util.ValidatePath(".", &cwd))
require.NoError(t, os.Setenv("PROJECT_ROOT", "."))
for i, file := range files { for i, file := range files {
files[i] = path.Join("testdata", name, file) files[i] = path.Join("testdata", name, file)
@ -65,7 +74,7 @@ func config(t *testing.T, name string, files []string, squadronName string, unit
} }
{ {
require.NoError(t, sq.FilterConfig(squadronName, unitNames), "failed to filter config") require.NoError(t, sq.FilterConfig(squadronName, unitNames, tags), "failed to filter config")
testutils.Snapshot(t, path.Join("testdata", name, "snapshop-config-norender.yaml"), sq.ConfigYAML()) testutils.Snapshot(t, path.Join("testdata", name, "snapshop-config-norender.yaml"), sq.ConfigYAML())
} }

View File

@ -0,0 +1,14 @@
version: "2.0"
squadron:
checkout:
backend:
chart:
name: backend
repository: file://<% env "PROJECT_ROOT" %>/_examples/common/charts/backend
version: 0.0.1
tags:
- backend
values:
image:
repository: nginx
tag: latest

14
testdata/tags/snapshop-config.yaml vendored Normal file
View File

@ -0,0 +1,14 @@
version: "2.0"
squadron:
checkout:
backend:
chart:
name: backend
repository: file://./_examples/common/charts/backend
version: 0.0.1
tags:
- backend
values:
image:
repository: nginx
tag: latest

49
testdata/tags/snapshop-template.yaml vendored Normal file
View File

@ -0,0 +1,49 @@
---
# Source: backend/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: backend
labels:
app.kubernetes.io/name: backend
app.kubernetes.io/component: backend
app.kubernetes.io/managed-by: Helm
helm.sh/chart: backend-0.0.1
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: backend
app.kubernetes.io/component: backend
ports:
- name: http
port: 80
---
# Source: backend/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
labels:
app.kubernetes.io/name: backend
app.kubernetes.io/component: backend
app.kubernetes.io/managed-by: Helm
helm.sh/chart: backend-0.0.1
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: backend
app.kubernetes.io/component: backend
template:
metadata:
labels:
app.kubernetes.io/name: backend
app.kubernetes.io/component: backend
spec:
containers:
- name: backend
image: nginx:latest
ports:
- name: http
protocol: TCP
containerPort: 80

33
testdata/tags/squadron.yaml vendored Normal file
View File

@ -0,0 +1,33 @@
version: "2.0"
squadron:
storefinder:
frontend:
chart: <% env "PROJECT_ROOT" %>/_examples/common/charts/frontend
tags: ["frontend"]
values:
image:
tag: latest
repository: nginx
backend:
chart: <% env "PROJECT_ROOT" %>/_examples/common/charts/backend
tags: ["backend", "skip"]
values:
image:
tag: latest
repository: nginx
checkout:
frontend:
chart: <% env "PROJECT_ROOT" %>/_examples/common/charts/frontend
tags: ["frontend"]
values:
image:
tag: latest
repository: nginx
backend:
chart: <% env "PROJECT_ROOT" %>/_examples/common/charts/backend
tags: ["backend"]
values:
image:
tag: latest
repository: nginx