Skip to content

Commit bb2dfc4

Browse files
authored
feat(build, imageSpec): support template werf variables (%image%, %project%) for labels (#6724)
Signed-off-by: Yaroslav Pershin <62902094+iapershin@users.noreply.github.com>
1 parent f1cdf19 commit bb2dfc4

File tree

5 files changed

+138
-12
lines changed

5 files changed

+138
-12
lines changed

Diff for: docs/pages_en/usage/build/images.md

+20
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,26 @@ imageSpec:
520520
PATH: "${PATH}:/app/bin"
521521
```
522522

523+
### Working with labels
524+
525+
When working with labels, you can use templating. The following variables are currently available:
526+
527+
- %image% – the name of the image
528+
- %project% – the name of the project
529+
530+
```yaml
531+
project: test
532+
configVersion: 1
533+
---
534+
image: backend
535+
from: alpine:3.21
536+
imageSpec:
537+
config:
538+
labels:
539+
frontend-version: "1.2.3"
540+
project-%project%: "%image%-image"
541+
```
542+
523543
## Linking images
524544

525545
### Inheritance and importing files

Diff for: docs/pages_ru/usage/build/images.md

+20
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,26 @@ imageSpec:
519519
PATH: "${PATH}:/app/bin"
520520
```
521521

522+
### Работа с лейблами образов
523+
524+
При работе с лейблами вы можете использовать шаблонизацию. На текущий момент доступны следующие переменные:
525+
526+
- `%image%` - имя образа
527+
- `%project%` - имя проекта
528+
529+
```yaml
530+
project: test
531+
configVersion: 1
532+
---
533+
image: backend
534+
from: alpine:3.21
535+
imageSpec:
536+
config:
537+
labels:
538+
frontend-version: "1.2.3"
539+
project-%project%: "%image%-image"
540+
```
541+
522542
## Взаимодействие между образами
523543

524544
### Наследование и импортирование файлов

Diff for: pkg/build/stage/image_spec.go

+61-7
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package stage
22

33
import (
4+
"bytes"
45
"context"
56
"fmt"
7+
"html/template"
68
"regexp"
79
"sort"
810
"strings"
@@ -17,6 +19,9 @@ import (
1719
)
1820

1921
const (
22+
labelTemplateImage = "image"
23+
labelTemplateProject = "project"
24+
labelTemplateDelimiter = "%"
2025
werfLabelsGlobalWarning = "Removal of the werf labels requires explicit use of the clearWerfLabels directive. Some labels are purely informational, while others are essential for cleanup operations."
2126
werfLabelsHostCleanupWarning = "Removal of the %s label will affect host auto cleanup. Proper work of auto cleanup is not guaranteed."
2227
)
@@ -78,9 +83,7 @@ func (s *ImageSpecStage) PrepareImage(ctx context.Context, _ Conveyor, _ contain
7883
newConfig.ClearEntrypoint = newConfig.ClearEntrypoint || s.imageSpec.ClearEntrypoint
7984
}
8085

81-
serviceLabels := s.stageImage.Image.GetBuildServiceLabels()
82-
mergedLabels := util.MergeMaps(s.imageSpec.Labels, serviceLabels)
83-
resultLabels, err := modifyLabels(ctx, mergedLabels, s.imageSpec.Labels, s.imageSpec.RemoveLabels, s.imageSpec.ClearWerfLabels)
86+
resultLabels, err := s.modifyLabels(ctx, imageInfo.Labels, s.imageSpec.Labels, s.imageSpec.RemoveLabels, s.imageSpec.ClearWerfLabels)
8487
if err != nil {
8588
return fmt.Errorf("unable to modify labels: %s", err)
8689
}
@@ -147,11 +150,30 @@ func (s *ImageSpecStage) GetDependencies(_ context.Context, _ Conveyor, _ contai
147150
return util.Sha256Hash(args...), nil
148151
}
149152

150-
func modifyLabels(ctx context.Context, labels, addLabels map[string]string, removeLabels []string, clearWerfLabels bool) (map[string]string, error) {
153+
func (s *ImageSpecStage) modifyLabels(ctx context.Context, labels, addLabels map[string]string, removeLabels []string, clearWerfLabels bool) (map[string]string, error) {
151154
if labels == nil {
152155
labels = make(map[string]string)
153156
}
154157

158+
serviceLabels := s.stageImage.Image.GetBuildServiceLabels()
159+
labels = util.MergeMaps(labels, serviceLabels)
160+
161+
processedAddLabels := make(map[string]string, len(addLabels))
162+
data := labelsTemplateData{
163+
Project: s.projectName,
164+
Image: s.imageName,
165+
}
166+
167+
for key, value := range addLabels {
168+
newKey, newValue, err := replaceLabelTemplate(key, value, data)
169+
if err != nil {
170+
return nil, err
171+
}
172+
processedAddLabels[newKey] = newValue
173+
}
174+
175+
labels = util.MergeMaps(labels, processedAddLabels)
176+
155177
exactMatches := make(map[string]struct{})
156178
var regexPatterns []*regexp.Regexp
157179

@@ -206,11 +228,43 @@ func modifyLabels(ctx context.Context, labels, addLabels map[string]string, remo
206228
global_warnings.GlobalWarningLn(ctx, fmt.Sprintf(werfLabelsHostCleanupWarning, strings.Join(cleanupWarnKeys, "','")))
207229
}
208230

209-
for key, value := range addLabels {
210-
labels[key] = value
231+
return labels, nil
232+
}
233+
234+
type labelsTemplateData struct {
235+
Project string
236+
Image string
237+
}
238+
239+
func replaceLabelTemplate(k, v string, data labelsTemplateData) (string, string, error) {
240+
funcMap := template.FuncMap{
241+
labelTemplateProject: func() string { return data.Project },
242+
labelTemplateImage: func() string { return data.Image },
211243
}
212244

213-
return labels, nil
245+
keyTmpl := template.New("key").Delims(labelTemplateDelimiter, labelTemplateDelimiter).Funcs(funcMap)
246+
valueTmpl := template.New("value").Delims(labelTemplateDelimiter, labelTemplateDelimiter).Funcs(funcMap)
247+
248+
parsedKeyTmpl, err := keyTmpl.Parse(k)
249+
if err != nil {
250+
return "", "", err
251+
}
252+
253+
parsedValueTmpl, err := valueTmpl.Parse(v)
254+
if err != nil {
255+
return "", "", err
256+
}
257+
258+
var keyBuf, valueBuf bytes.Buffer
259+
260+
if err := parsedKeyTmpl.Execute(&keyBuf, data); err != nil {
261+
return "", "", err
262+
}
263+
264+
if err := parsedValueTmpl.Execute(&valueBuf, data); err != nil {
265+
return "", "", err
266+
}
267+
return keyBuf.String(), valueBuf.String(), nil
214268
}
215269

216270
func modifyEnv(env, removeKeys []string, addKeysMap map[string]string) ([]string, error) {

Diff for: pkg/build/stage/image_spec_test.go

+31-5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/stretchr/testify/assert"
88

99
"github.com/werf/werf/v2/pkg/config"
10+
"github.com/werf/werf/v2/pkg/container_backend"
1011
)
1112

1213
func TestEnvExpander(t *testing.T) {
@@ -97,6 +98,7 @@ func TestModifyLabels(t *testing.T) {
9798
clearWerfLabels: false,
9899
expectedLabels: map[string]string{
99100
"werf": "should-stay",
101+
"stub": "true",
100102
},
101103
},
102104
{
@@ -109,6 +111,7 @@ func TestModifyLabels(t *testing.T) {
109111
clearWerfLabels: false,
110112
expectedLabels: map[string]string{
111113
"test-label": "bar",
114+
"stub": "true",
112115
},
113116
},
114117
{
@@ -124,6 +127,7 @@ func TestModifyLabels(t *testing.T) {
124127
expectedLabels: map[string]string{
125128
"test-label": "bar",
126129
"not-werf": "keep",
130+
"stub": "true",
127131
},
128132
},
129133
{
@@ -139,6 +143,7 @@ func TestModifyLabels(t *testing.T) {
139143
"test-label": "bar",
140144
"new": "value",
141145
"other": "123",
146+
"stub": "true",
142147
},
143148
},
144149
{
@@ -152,12 +157,15 @@ func TestModifyLabels(t *testing.T) {
152157
removeLabels: []string{"remove"},
153158
clearWerfLabels: true,
154159
addLabels: map[string]string{
155-
"new": "added",
160+
"new": "added",
161+
"project-%project%-id": "image-%image%-name",
156162
},
157163
expectedLabels: map[string]string{
158-
"test-label": "bar",
159-
"keep": "me",
160-
"new": "added",
164+
"test-label": "bar",
165+
"keep": "me",
166+
"new": "added",
167+
"project-TEST-PROJECT-id": "image-TEST-IMAGE-name",
168+
"stub": "true",
161169
},
162170
},
163171
}
@@ -169,13 +177,31 @@ func TestModifyLabels(t *testing.T) {
169177
labelsCopy[k] = v
170178
}
171179

172-
modifiedLabels, err := modifyLabels(ctx, labelsCopy, tt.addLabels, tt.removeLabels, tt.clearWerfLabels)
180+
s := ImageSpecStage{
181+
BaseStage: &BaseStage{
182+
projectName: "TEST-PROJECT",
183+
imageName: "TEST-IMAGE",
184+
stageImage: &StageImage{
185+
Image: NewLegacyImageStub(),
186+
},
187+
},
188+
}
189+
190+
modifiedLabels, err := s.modifyLabels(ctx, labelsCopy, tt.addLabels, tt.removeLabels, tt.clearWerfLabels)
173191
assert.NoError(t, err)
174192
assert.Equal(t, tt.expectedLabels, modifiedLabels)
175193
})
176194
}
177195
}
178196

197+
type MockImage struct {
198+
Labels map[string]string
199+
}
200+
201+
func (i *MockImage) Build(_ context.Context, _ container_backend.BuildOptions) error {
202+
return nil
203+
}
204+
179205
func TestModifyVolumes(t *testing.T) {
180206
tests := []struct {
181207
name string

Diff for: pkg/build/stage/stubs.go

+6
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ func (img *LegacyImageStub) Container() container_backend.LegacyContainer {
3030
return img._Container
3131
}
3232

33+
func (img *LegacyImageStub) GetBuildServiceLabels() map[string]string {
34+
return map[string]string{
35+
"stub": "true",
36+
}
37+
}
38+
3339
type LegacyContainerStub struct {
3440
container_backend.LegacyContainer
3541

0 commit comments

Comments
 (0)