Skip to content

Commit 36f3d6a

Browse files
feat(build): add clearUser and clearWorkingDir options to imageSpec config (#6759)
Signed-off-by: Yaroslav Pershin <62902094+iapershin@users.noreply.github.com> Co-authored-by: Aleksei Igrychev <aleksei.igrychev@palark.com>
1 parent 63dcbfb commit 36f3d6a

File tree

13 files changed

+506
-173
lines changed

13 files changed

+506
-173
lines changed

Diff for: docs/_data/werf_yaml.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ sections:
425425
ru: Список переменных окружения для добавления
426426
detailsLink: "https://docs.docker.com/engine/reference/builder/#env"
427427
- name: removeEnv
428-
value: "[string, ...]"
428+
value: "[ string || /REGEXP/, ... ]"
429429
description:
430430
en: List of environment variables to remove
431431
ru: Список переменных окружения для удаления

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

+145-96
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ func GenerateImageSpecStage(imageSpec *config.ImageSpec, baseStageOptions *BaseS
3939
}
4040

4141
func newImageSpecStage(imageSpec *config.ImageSpec, baseStageOptions *BaseStageOptions) *ImageSpecStage {
42-
s := &ImageSpecStage{}
43-
s.imageSpec = imageSpec
44-
s.BaseStage = NewBaseStage(ImageSpec, baseStageOptions)
45-
return s
42+
return &ImageSpecStage{
43+
imageSpec: imageSpec,
44+
BaseStage: NewBaseStage(ImageSpec, baseStageOptions),
45+
}
4646
}
4747

4848
func (s *ImageSpecStage) IsBuildable() bool {
@@ -56,67 +56,55 @@ func (s *ImageSpecStage) IsMutable() bool {
5656
func (s *ImageSpecStage) PrepareImage(ctx context.Context, _ Conveyor, _ container_backend.ContainerBackend, prevBuiltImage, stageImage *StageImage, _ container_backend.BuildContextArchiver) error {
5757
if s.imageSpec != nil {
5858
imageInfo := prevBuiltImage.Image.GetStageDesc().Info
59+
newConfig := s.baseConfig()
5960

60-
newConfig := image.Config{
61-
Author: s.imageSpec.Author,
62-
User: s.imageSpec.User,
63-
Entrypoint: s.imageSpec.Entrypoint,
64-
Cmd: s.imageSpec.Cmd,
65-
WorkingDir: s.imageSpec.WorkingDir,
66-
StopSignal: s.imageSpec.StopSignal,
67-
}
68-
69-
// Entrypoint and Cmd handling.
7061
{
71-
// If CMD is defined from the base image, setting ENTRYPOINT will reset CMD to an empty value.
72-
// In this scenario, CMD must be defined in the current image to have a value.
73-
// rel https://docs.docker.com/reference/dockerfile/#understand-how-cmd-and-entrypoint-interact
74-
if s.imageSpec.Entrypoint != nil {
75-
newConfig.Entrypoint = s.imageSpec.Entrypoint
76-
if s.imageSpec.Cmd == nil {
77-
newConfig.ClearCmd = true
78-
}
79-
}
80-
81-
if s.imageSpec.Cmd != nil {
82-
newConfig.Cmd = s.imageSpec.Cmd
62+
// labels
63+
resultLabels, err := s.modifyLabels(ctx, imageInfo.Labels, s.imageSpec.Labels, s.imageSpec.RemoveLabels, s.imageSpec.KeepEssentialWerfLabels)
64+
if err != nil {
65+
return fmt.Errorf("unable to modify labels: %s", err)
8366
}
84-
85-
newConfig.ClearCmd = newConfig.ClearCmd || s.imageSpec.ClearCmd
86-
newConfig.ClearEntrypoint = newConfig.ClearEntrypoint || s.imageSpec.ClearEntrypoint
67+
newConfig.Labels = resultLabels
8768
}
8869

89-
resultLabels, err := s.modifyLabels(ctx, imageInfo.Labels, s.imageSpec.Labels, s.imageSpec.RemoveLabels, s.imageSpec.KeepEssentialWerfLabels)
90-
if err != nil {
91-
return fmt.Errorf("unable to modify labels: %s", err)
70+
{
71+
// envs
72+
resultEnvs, err := modifyEnv(imageInfo.Env, s.imageSpec.RemoveEnv, s.imageSpec.Env)
73+
if err != nil {
74+
return fmt.Errorf("unable to modify env: %w", err)
75+
}
76+
newConfig.Env = resultEnvs
9277
}
9378

94-
newConfig.Labels = resultLabels
95-
newConfig.Env, err = modifyEnv(imageInfo.Env, s.imageSpec.RemoveEnv, s.imageSpec.Env)
96-
if err != nil {
97-
return fmt.Errorf("unable to modify env: %w", err)
79+
{
80+
// volumes
81+
newConfig.Volumes = modifyVolumes(imageInfo.Volumes, s.imageSpec.RemoveVolumes, s.imageSpec.Volumes)
9882
}
99-
newConfig.Volumes = modifyVolumes(imageInfo.Volumes, s.imageSpec.RemoveVolumes, s.imageSpec.Volumes)
10083

101-
if s.imageSpec.Expose != nil {
102-
newConfig.ExposedPorts = make(map[string]struct{}, len(s.imageSpec.Expose))
103-
for _, expose := range s.imageSpec.Expose {
104-
newConfig.ExposedPorts[expose] = struct{}{}
84+
{
85+
// expose
86+
if s.imageSpec.Expose != nil {
87+
newConfig.ExposedPorts = make(map[string]struct{}, len(s.imageSpec.Expose))
88+
for _, expose := range s.imageSpec.Expose {
89+
newConfig.ExposedPorts[expose] = struct{}{}
90+
}
10591
}
10692
}
10793

108-
if s.imageSpec.Healthcheck != nil {
109-
newConfig.HealthConfig = &image.HealthConfig{
110-
Test: s.imageSpec.Healthcheck.Test,
111-
Interval: toDuration(s.imageSpec.Healthcheck.Interval),
112-
Timeout: toDuration(s.imageSpec.Healthcheck.Timeout),
113-
StartPeriod: toDuration(s.imageSpec.Healthcheck.StartPeriod),
114-
Retries: s.imageSpec.Healthcheck.Retries,
94+
{
95+
// healthcheck
96+
if s.imageSpec.Healthcheck != nil {
97+
newConfig.HealthConfig = &image.HealthConfig{
98+
Test: s.imageSpec.Healthcheck.Test,
99+
Interval: toDuration(s.imageSpec.Healthcheck.Interval),
100+
Timeout: toDuration(s.imageSpec.Healthcheck.Timeout),
101+
StartPeriod: toDuration(s.imageSpec.Healthcheck.StartPeriod),
102+
Retries: s.imageSpec.Healthcheck.Retries,
103+
}
115104
}
116105
}
117106

118-
newConfig.ClearHistory = s.imageSpec.ClearHistory
119-
107+
// set config
120108
stageImage.Image.SetImageSpecConfig(&newConfig)
121109
}
122110

@@ -150,72 +138,79 @@ func (s *ImageSpecStage) GetDependencies(_ context.Context, _ Conveyor, _ contai
150138
args = append(args, s.imageSpec.StopSignal)
151139
args = append(args, fmt.Sprint(s.imageSpec.Healthcheck))
152140

153-
return util.Sha256Hash(args...), nil
154-
}
155-
156-
func (s *ImageSpecStage) modifyLabels(ctx context.Context, labels, addLabels map[string]string, removeLabels []string, keepEssentialWerfLabels bool) (map[string]string, error) {
157-
if labels == nil {
158-
labels = make(map[string]string)
141+
if s.imageSpec.ClearUser {
142+
args = append(args, fmt.Sprint(s.imageSpec.ClearUser))
159143
}
160144

161-
serviceLabels := s.stageImage.Image.GetBuildServiceLabels()
162-
labels = util.MergeMaps(labels, serviceLabels)
163-
164-
processedAddLabels := make(map[string]string, len(addLabels))
165-
data := labelsTemplateData{
166-
Project: s.projectName,
167-
Image: s.imageName,
145+
if s.imageSpec.ClearWorkingDir {
146+
args = append(args, fmt.Sprint(s.imageSpec.ClearWorkingDir))
168147
}
169148

170-
for key, value := range addLabels {
171-
newKey, newValue, err := replaceLabelTemplate(key, value, data)
172-
if err != nil {
173-
return nil, err
174-
}
175-
processedAddLabels[newKey] = newValue
149+
if s.imageSpec.KeepEssentialWerfLabels {
150+
args = append(args, fmt.Sprint(s.imageSpec.KeepEssentialWerfLabels))
176151
}
177152

178-
labels = util.MergeMaps(labels, processedAddLabels)
153+
return util.Sha256Hash(args...), nil
154+
}
179155

180-
exactMatches := make(map[string]struct{})
181-
var regexPatterns []*regexp.Regexp
156+
func (s *ImageSpecStage) baseConfig() image.Config {
157+
newConfig := image.Config{
158+
Author: s.imageSpec.Author,
159+
User: s.imageSpec.User,
160+
Entrypoint: s.imageSpec.Entrypoint,
161+
Cmd: s.imageSpec.Cmd,
162+
WorkingDir: s.imageSpec.WorkingDir,
163+
StopSignal: s.imageSpec.StopSignal,
164+
ClearHistory: s.imageSpec.ClearHistory,
165+
ClearUser: s.imageSpec.ClearUser,
166+
ClearWorkingDir: s.imageSpec.ClearWorkingDir,
167+
}
182168

183-
for _, pattern := range removeLabels {
184-
if strings.HasPrefix(pattern, "/") && strings.HasSuffix(pattern, "/") {
185-
expr := fmt.Sprintf("^%s$", pattern[1:len(pattern)-1])
186-
re, err := regexp.Compile(expr)
187-
if err != nil {
188-
return nil, err
169+
// Entrypoint and Cmd handling.
170+
{
171+
// If CMD is defined from the base image, setting ENTRYPOINT will reset CMD to an empty value.
172+
// In this scenario, CMD must be defined in the current image to have a value.
173+
// rel https://docs.docker.com/reference/dockerfile/#understand-how-cmd-and-entrypoint-interact
174+
if s.imageSpec.Entrypoint != nil {
175+
newConfig.Entrypoint = s.imageSpec.Entrypoint
176+
if s.imageSpec.Cmd == nil {
177+
newConfig.ClearCmd = true
189178
}
190-
regexPatterns = append(regexPatterns, re)
191-
} else {
192-
exactMatches[pattern] = struct{}{}
193179
}
194-
}
195180

196-
matchFunc := func(key string) bool {
197-
if _, found := exactMatches[key]; found {
198-
return true
181+
if s.imageSpec.Cmd != nil {
182+
newConfig.Cmd = s.imageSpec.Cmd
199183
}
200-
for _, re := range regexPatterns {
201-
if re.MatchString(key) {
202-
return true
203-
}
204-
}
205-
return false
184+
185+
newConfig.ClearCmd = newConfig.ClearCmd || s.imageSpec.ClearCmd
186+
newConfig.ClearEntrypoint = newConfig.ClearEntrypoint || s.imageSpec.ClearEntrypoint
187+
}
188+
return newConfig
189+
}
190+
191+
func (s *ImageSpecStage) modifyLabels(ctx context.Context, labels, addLabels map[string]string, removeLabels []string, keepEssentialWerfLabels bool) (map[string]string, error) {
192+
if labels == nil {
193+
labels = make(map[string]string)
194+
}
195+
196+
serviceLabels := s.stageImage.Image.GetBuildServiceLabels()
197+
labels = util.MergeMaps(labels, serviceLabels)
198+
199+
exactMatches, regexPatterns, err := compileRemovePatterns(removeLabels)
200+
if err != nil {
201+
return nil, err
206202
}
207203

208204
shouldPrintGlobalWarn := false
209205
for key := range labels {
210-
if !matchFunc(key) {
206+
if !matchKey(key, exactMatches, regexPatterns) {
211207
continue
212208
}
213209

214210
if key == image.WerfLabel || key == image.WerfParentStageID {
215211
if !keepEssentialWerfLabels {
216212
shouldPrintGlobalWarn = true
217213
}
218-
219214
continue
220215
}
221216

@@ -226,6 +221,22 @@ func (s *ImageSpecStage) modifyLabels(ctx context.Context, labels, addLabels map
226221
global_warnings.GlobalWarningLn(ctx, werfLabelsGlobalWarning)
227222
}
228223

224+
processedAddLabels := make(map[string]string, len(addLabels))
225+
data := labelsTemplateData{
226+
Project: s.projectName,
227+
Image: s.imageName,
228+
}
229+
230+
for key, value := range addLabels {
231+
newKey, newValue, err := replaceLabelTemplate(key, value, data)
232+
if err != nil {
233+
return nil, err
234+
}
235+
processedAddLabels[newKey] = newValue
236+
}
237+
238+
labels = util.MergeMaps(labels, processedAddLabels)
239+
229240
return labels, nil
230241
}
231242

@@ -281,11 +292,17 @@ func modifyEnv(env, removeKeys []string, addKeysMap map[string]string) ([]string
281292
"WERF_COMMIT_TIME_UNIX",
282293
}...)
283294

284-
for _, key := range removeKeys {
285-
delete(baseEnvMap, key)
295+
exactMatches, regexPatterns, err := compileRemovePatterns(removeKeys)
296+
if err != nil {
297+
return nil, err
298+
}
299+
300+
for key := range baseEnvMap {
301+
if matchKey(key, exactMatches, regexPatterns) {
302+
delete(baseEnvMap, key)
303+
}
286304
}
287305

288-
// FIXME: (v3) This is a temporary solution to remove werf SSH_AUTH_SOCK that persist after build.
289306
if envValue, hasEnv := baseEnvMap[ssh_agent.SSHAuthSockEnv]; hasEnv && envValue == container_backend.SSHContainerAuthSockPath {
290307
delete(baseEnvMap, ssh_agent.SSHAuthSockEnv)
291308
}
@@ -330,3 +347,35 @@ func sortSliceWithNewSlice(original []string) []string {
330347
func toDuration(seconds int) time.Duration {
331348
return time.Duration(seconds) * time.Second
332349
}
350+
351+
func compileRemovePatterns(removePatterns []string) (map[string]struct{}, []*regexp.Regexp, error) {
352+
exactMatches := make(map[string]struct{})
353+
var regexPatterns []*regexp.Regexp
354+
355+
for _, pattern := range removePatterns {
356+
if strings.HasPrefix(pattern, "/") && strings.HasSuffix(pattern, "/") {
357+
expr := fmt.Sprintf("^%s$", pattern[1:len(pattern)-1])
358+
re, err := regexp.Compile(expr)
359+
if err != nil {
360+
return nil, nil, err
361+
}
362+
regexPatterns = append(regexPatterns, re)
363+
} else {
364+
exactMatches[pattern] = struct{}{}
365+
}
366+
}
367+
368+
return exactMatches, regexPatterns, nil
369+
}
370+
371+
func matchKey(key string, exactMatches map[string]struct{}, regexPatterns []*regexp.Regexp) bool {
372+
if _, found := exactMatches[key]; found {
373+
return true
374+
}
375+
for _, re := range regexPatterns {
376+
if re.MatchString(key) {
377+
return true
378+
}
379+
}
380+
return false
381+
}

0 commit comments

Comments
 (0)