-
Notifications
You must be signed in to change notification settings - Fork 5.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable configs.file's on remote docker hosts #11871
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,10 @@ import ( | |
"bytes" | ||
"context" | ||
"fmt" | ||
"io" | ||
"io/fs" | ||
"os" | ||
"path/filepath" | ||
"strconv" | ||
"time" | ||
|
||
|
@@ -30,27 +34,36 @@ import ( | |
|
||
func (s *composeService) injectSecrets(ctx context.Context, project *types.Project, service types.ServiceConfig, id string) error { | ||
for _, config := range service.Secrets { | ||
file := project.Secrets[config.Source] | ||
if file.Environment == "" { | ||
continue | ||
} | ||
|
||
if config.Target == "" { | ||
config.Target = "/run/secrets/" + config.Source | ||
} else if !isAbsTarget(config.Target) { | ||
config.Target = "/run/secrets/" + config.Target | ||
} | ||
|
||
env, ok := project.Environment[file.Environment] | ||
if !ok { | ||
return fmt.Errorf("environment variable %q required by file %q is not set", file.Environment, file.Name) | ||
file := project.Secrets[config.Source] | ||
var tarArchive bytes.Buffer | ||
var err error | ||
switch { | ||
case file.File != "": | ||
tarArchive, err = createTarArchiveOf(file.File, types.FileReferenceConfig(config)) | ||
case file.Environment != "": | ||
env, ok := project.Environment[file.Environment] | ||
if !ok { | ||
return fmt.Errorf("environment variable %q required by file %q is not set", file.Environment, file.Name) | ||
} | ||
tarArchive, err = createTarredFileOf(env, types.FileReferenceConfig(config)) | ||
} | ||
b, err := createTar(env, types.FileReferenceConfig(config)) | ||
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
err = s.apiClient().CopyToContainer(ctx, id, "/", &b, moby.CopyToContainerOptions{ | ||
// secret was handled elsewhere (e.g it was external) | ||
if tarArchive.Len() == 0 { | ||
continue | ||
} | ||
|
||
err = s.apiClient().CopyToContainer(ctx, id, "/", &tarArchive, moby.CopyToContainerOptions{ | ||
CopyUIDGID: config.UID != "" || config.GID != "", | ||
}) | ||
if err != nil { | ||
|
@@ -62,29 +75,36 @@ func (s *composeService) injectSecrets(ctx context.Context, project *types.Proje | |
|
||
func (s *composeService) injectConfigs(ctx context.Context, project *types.Project, service types.ServiceConfig, id string) error { | ||
for _, config := range service.Configs { | ||
if config.Target == "" { | ||
config.Target = "/" + config.Source | ||
} | ||
|
||
file := project.Configs[config.Source] | ||
content := file.Content | ||
if file.Environment != "" { | ||
var tarArchive bytes.Buffer | ||
var err error | ||
switch { | ||
case file.File != "": | ||
tarArchive, err = createTarArchiveOf(file.File, types.FileReferenceConfig(config)) | ||
case file.Content != "": | ||
tarArchive, err = createTarredFileOf(file.Content, types.FileReferenceConfig(config)) | ||
case file.Environment != "": | ||
env, ok := project.Environment[file.Environment] | ||
if !ok { | ||
return fmt.Errorf("environment variable %q required by file %q is not set", file.Environment, file.Name) | ||
} | ||
content = env | ||
} | ||
if content == "" { | ||
continue | ||
} | ||
|
||
if config.Target == "" { | ||
config.Target = "/" + config.Source | ||
tarArchive, err = createTarredFileOf(env, types.FileReferenceConfig(config)) | ||
} | ||
|
||
b, err := createTar(content, types.FileReferenceConfig(config)) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
err = s.apiClient().CopyToContainer(ctx, id, "/", &b, moby.CopyToContainerOptions{ | ||
// config was handled elsewhere (e.g it was external) | ||
if tarArchive.Len() == 0 { | ||
continue | ||
} | ||
|
||
err = s.apiClient().CopyToContainer(ctx, id, "/", &tarArchive, moby.CopyToContainerOptions{ | ||
CopyUIDGID: config.UID != "" || config.GID != "", | ||
}) | ||
if err != nil { | ||
|
@@ -94,47 +114,135 @@ func (s *composeService) injectConfigs(ctx context.Context, project *types.Proje | |
return nil | ||
} | ||
|
||
func createTar(env string, config types.FileReferenceConfig) (bytes.Buffer, error) { | ||
value := []byte(env) | ||
func createTarredFileOf(value string, config types.FileReferenceConfig) (bytes.Buffer, error) { | ||
mode, uid, gid, err := makeTarFileEntryParams(config) | ||
if err != nil { | ||
return bytes.Buffer{}, fmt.Errorf("failed parsing target file parameters") | ||
} | ||
|
||
b := bytes.Buffer{} | ||
tarWriter := tar.NewWriter(&b) | ||
mode := uint32(0o444) | ||
valueAsBytes := []byte(value) | ||
header := &tar.Header{ | ||
Name: config.Target, | ||
Size: int64(len(valueAsBytes)), | ||
Mode: mode, | ||
ModTime: time.Now(), | ||
Uid: uid, | ||
Gid: gid, | ||
} | ||
err = tarWriter.WriteHeader(header) | ||
if err != nil { | ||
return bytes.Buffer{}, err | ||
} | ||
_, err = tarWriter.Write(valueAsBytes) | ||
if err != nil { | ||
return bytes.Buffer{}, err | ||
} | ||
err = tarWriter.Close() | ||
return b, err | ||
} | ||
|
||
func createTarArchiveOf(path string, config types.FileReferenceConfig) (bytes.Buffer, error) { | ||
// need to treat files and directories differently | ||
fi, err := os.Stat(path) | ||
if err != nil { | ||
return bytes.Buffer{}, err | ||
} | ||
|
||
// if path is not directory, try to treat it as a file by reading its value | ||
if !fi.IsDir() { | ||
buf, err := os.ReadFile(path) | ||
if err == nil { | ||
return createTarredFileOf(string(buf), config) | ||
} | ||
} | ||
|
||
mode, uid, gid, err := makeTarFileEntryParams(config) | ||
if err != nil { | ||
return bytes.Buffer{}, fmt.Errorf("failed parsing target file parameters") | ||
} | ||
|
||
subdir := os.DirFS(path) | ||
b := bytes.Buffer{} | ||
tarWriter := tar.NewWriter(&b) | ||
|
||
// build the tar by walking instead of using archive/tar.Writer.AddFS to be able to adjust mode, gid and uid | ||
err = fs.WalkDir(subdir, ".", func(filePath string, d fs.DirEntry, err error) error { | ||
header := &tar.Header{ | ||
Name: filepath.Join(config.Target, filePath), | ||
Mode: mode, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❓ Should directories have the same mode as files? Or should they perhaps have the exec bit set for the owner/group/other to access them? |
||
ModTime: time.Now(), | ||
Uid: uid, | ||
Gid: gid, | ||
} | ||
|
||
if d.IsDir() { | ||
// tar requires that directory headers ends with a slash | ||
header.Name = header.Name + "/" | ||
err = tarWriter.WriteHeader(header) | ||
if err != nil { | ||
return fmt.Errorf("failed writing tar header of directory %v while walking diretory structure, error was: %w", filePath, err) | ||
} | ||
} else { | ||
f, err := subdir.Open(filePath) | ||
if err != nil { | ||
return err | ||
} | ||
defer f.Close() | ||
|
||
valueAsBytes, err := io.ReadAll(f) | ||
if err != nil { | ||
return fmt.Errorf("failed reading file %v for to send to container, error was: %w", filePath, err) | ||
} | ||
|
||
header.Size = int64(len(valueAsBytes)) | ||
err = tarWriter.WriteHeader(header) | ||
if err != nil { | ||
return fmt.Errorf("failed writing tar header for file %v while walking diretory structure, error was: %w", filePath, err) | ||
} | ||
|
||
_, err = tarWriter.Write(valueAsBytes) | ||
if err != nil { | ||
return fmt.Errorf("failed writing file content of %v into tar archive while walking directory structure, error was: %w", filePath, err) | ||
} | ||
} | ||
|
||
return nil | ||
}) | ||
|
||
if err != nil { | ||
return bytes.Buffer{}, fmt.Errorf("failed building tar archive while walking config directory structure, error was: %w", err) | ||
} | ||
|
||
err = tarWriter.Close() | ||
if err != nil { | ||
return bytes.Buffer{}, fmt.Errorf("failed closing tar archive after writing, error was: %w", err) | ||
} | ||
|
||
return b, err | ||
} | ||
|
||
func makeTarFileEntryParams(config types.FileReferenceConfig) (mode int64, uid, gid int, err error) { | ||
mode = 0o444 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❓ should the default mode be different for secrets compared to configs? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would make sense to remove at least public read ( |
||
if config.Mode != nil { | ||
mode = *config.Mode | ||
mode = int64(*config.Mode) | ||
} | ||
|
||
var uid, gid int | ||
if config.UID != "" { | ||
v, err := strconv.Atoi(config.UID) | ||
if err != nil { | ||
return b, err | ||
return 0, 0, 0, err | ||
} | ||
uid = v | ||
} | ||
if config.GID != "" { | ||
v, err := strconv.Atoi(config.GID) | ||
if err != nil { | ||
return b, err | ||
return 0, 0, 0, err | ||
} | ||
gid = v | ||
} | ||
|
||
header := &tar.Header{ | ||
Name: config.Target, | ||
Size: int64(len(value)), | ||
Mode: int64(mode), | ||
ModTime: time.Now(), | ||
Uid: uid, | ||
Gid: gid, | ||
} | ||
err := tarWriter.WriteHeader(header) | ||
if err != nil { | ||
return bytes.Buffer{}, err | ||
} | ||
_, err = tarWriter.Write(value) | ||
if err != nil { | ||
return bytes.Buffer{}, err | ||
} | ||
err = tarWriter.Close() | ||
return b, err | ||
return mode, uid, gid, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: not handling file.Content as this seems to be rejected some other place, causing the printout "validating docker-compose.yml: secrets.content_secret Additional property content is not allowed"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it comes from the compose-spec.
There is no
content
property insecrets
, only inconfigs
.This correspond to the doc where secrets can only use
file
orenvironment
.I assume the reason is to prevent writing secrets value directly in the compose file.