mirror of
https://github.com/fluxcd/flux2.git
synced 2026-02-26 01:15:00 +00:00
Merge 46ad3b2e7a into 7132eb3435
This commit is contained in:
commit
3657a85382
3 changed files with 186 additions and 14 deletions
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
@ -48,9 +49,10 @@ from the given directory or a single manifest file.`,
|
||||||
}
|
}
|
||||||
|
|
||||||
type buildArtifactFlags struct {
|
type buildArtifactFlags struct {
|
||||||
output string
|
output string
|
||||||
path string
|
path string
|
||||||
ignorePaths []string
|
ignorePaths []string
|
||||||
|
resolveSymlinks bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var excludeOCI = append(strings.Split(sourceignore.ExcludeVCS, ","), strings.Split(sourceignore.ExcludeExt, ",")...)
|
var excludeOCI = append(strings.Split(sourceignore.ExcludeVCS, ","), strings.Split(sourceignore.ExcludeExt, ",")...)
|
||||||
|
|
@ -61,6 +63,7 @@ func init() {
|
||||||
buildArtifactCmd.Flags().StringVarP(&buildArtifactArgs.path, "path", "p", "", "Path to the directory where the Kubernetes manifests are located.")
|
buildArtifactCmd.Flags().StringVarP(&buildArtifactArgs.path, "path", "p", "", "Path to the directory where the Kubernetes manifests are located.")
|
||||||
buildArtifactCmd.Flags().StringVarP(&buildArtifactArgs.output, "output", "o", "artifact.tgz", "Path to where the artifact tgz file should be written.")
|
buildArtifactCmd.Flags().StringVarP(&buildArtifactArgs.output, "output", "o", "artifact.tgz", "Path to where the artifact tgz file should be written.")
|
||||||
buildArtifactCmd.Flags().StringSliceVar(&buildArtifactArgs.ignorePaths, "ignore-paths", excludeOCI, "set paths to ignore in .gitignore format")
|
buildArtifactCmd.Flags().StringSliceVar(&buildArtifactArgs.ignorePaths, "ignore-paths", excludeOCI, "set paths to ignore in .gitignore format")
|
||||||
|
buildArtifactCmd.Flags().BoolVar(&buildArtifactArgs.resolveSymlinks, "resolve-symlinks", false, "resolve symlinks by copying their targets into the artifact")
|
||||||
|
|
||||||
buildCmd.AddCommand(buildArtifactCmd)
|
buildCmd.AddCommand(buildArtifactCmd)
|
||||||
}
|
}
|
||||||
|
|
@ -85,6 +88,15 @@ func buildArtifactCmdRun(cmd *cobra.Command, args []string) error {
|
||||||
return fmt.Errorf("invalid path '%s', must point to an existing directory or file", path)
|
return fmt.Errorf("invalid path '%s', must point to an existing directory or file", path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if buildArtifactArgs.resolveSymlinks {
|
||||||
|
resolved, err := resolveSymlinks(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("resolving symlinks failed: %w", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(resolved)
|
||||||
|
path = resolved
|
||||||
|
}
|
||||||
|
|
||||||
logger.Actionf("building artifact from %s", path)
|
logger.Actionf("building artifact from %s", path)
|
||||||
|
|
||||||
ociClient := oci.NewClient(oci.DefaultOptions())
|
ociClient := oci.NewClient(oci.DefaultOptions())
|
||||||
|
|
@ -96,6 +108,110 @@ func buildArtifactCmdRun(cmd *cobra.Command, args []string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resolveSymlinks creates a temporary directory with symlinks resolved to their
|
||||||
|
// real file contents. This allows building artifacts from symlink trees (e.g.,
|
||||||
|
// those created by Nix) where the actual files live outside the source directory.
|
||||||
|
func resolveSymlinks(srcPath string) (string, error) {
|
||||||
|
absPath, err := filepath.Abs(srcPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := os.Stat(absPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// For a single file, resolve the symlink and return a temp dir containing it
|
||||||
|
if !info.IsDir() {
|
||||||
|
resolved, err := filepath.EvalSymlinks(absPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("resolving symlink for %s: %w", absPath, err)
|
||||||
|
}
|
||||||
|
tmpDir, err := os.MkdirTemp("", "flux-artifact-*")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
dst := filepath.Join(tmpDir, filepath.Base(absPath))
|
||||||
|
if err := copyFile(resolved, dst); err != nil {
|
||||||
|
os.RemoveAll(tmpDir)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return tmpDir, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpDir, err := os.MkdirTemp("", "flux-artifact-*")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = filepath.Walk(absPath, func(p string, fi os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
relPath, err := filepath.Rel(absPath, p)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dstPath := filepath.Join(tmpDir, relPath)
|
||||||
|
|
||||||
|
// Resolve symlinks to get the real file info
|
||||||
|
realPath := p
|
||||||
|
realInfo := fi
|
||||||
|
if fi.Mode()&os.ModeSymlink != 0 {
|
||||||
|
realPath, err = filepath.EvalSymlinks(p)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("resolving symlink %s: %w", p, err)
|
||||||
|
}
|
||||||
|
realInfo, err = os.Stat(realPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("stat resolved path %s: %w", realPath, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if realInfo.IsDir() {
|
||||||
|
return os.MkdirAll(dstPath, realInfo.Mode())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !realInfo.Mode().IsRegular() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return copyFile(realPath, dstPath)
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
os.RemoveAll(tmpDir)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmpDir, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyFile(src, dst string) error {
|
||||||
|
in, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer in.Close()
|
||||||
|
|
||||||
|
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
if _, err := io.Copy(out, in); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return out.Close()
|
||||||
|
}
|
||||||
|
|
||||||
func saveReaderToFile(reader io.Reader) (string, error) {
|
func saveReaderToFile(reader io.Reader) (string, error) {
|
||||||
b, err := io.ReadAll(bufio.NewReader(reader))
|
b, err := io.ReadAll(bufio.NewReader(reader))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
|
@ -68,3 +69,47 @@ data:
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_resolveSymlinks(t *testing.T) {
|
||||||
|
g := NewWithT(t)
|
||||||
|
|
||||||
|
// Create source directory with a real file
|
||||||
|
srcDir := t.TempDir()
|
||||||
|
realFile := filepath.Join(srcDir, "real.yaml")
|
||||||
|
g.Expect(os.WriteFile(realFile, []byte("apiVersion: v1\nkind: Namespace\nmetadata:\n name: test\n"), 0o644)).To(Succeed())
|
||||||
|
|
||||||
|
// Create a directory with symlinks pointing to files outside it
|
||||||
|
symlinkDir := t.TempDir()
|
||||||
|
symlinkFile := filepath.Join(symlinkDir, "linked.yaml")
|
||||||
|
g.Expect(os.Symlink(realFile, symlinkFile)).To(Succeed())
|
||||||
|
|
||||||
|
// Also add a regular file in the symlink dir
|
||||||
|
regularFile := filepath.Join(symlinkDir, "regular.yaml")
|
||||||
|
g.Expect(os.WriteFile(regularFile, []byte("apiVersion: v1\nkind: ConfigMap\n"), 0o644)).To(Succeed())
|
||||||
|
|
||||||
|
// Create a symlinked subdirectory
|
||||||
|
subDir := filepath.Join(srcDir, "subdir")
|
||||||
|
g.Expect(os.MkdirAll(subDir, 0o755)).To(Succeed())
|
||||||
|
g.Expect(os.WriteFile(filepath.Join(subDir, "nested.yaml"), []byte("nested"), 0o644)).To(Succeed())
|
||||||
|
g.Expect(os.Symlink(subDir, filepath.Join(symlinkDir, "linkeddir"))).To(Succeed())
|
||||||
|
|
||||||
|
// Resolve symlinks
|
||||||
|
resolved, err := resolveSymlinks(symlinkDir)
|
||||||
|
g.Expect(err).To(BeNil())
|
||||||
|
t.Cleanup(func() { os.RemoveAll(resolved) })
|
||||||
|
|
||||||
|
// Verify the regular file was copied
|
||||||
|
content, err := os.ReadFile(filepath.Join(resolved, "regular.yaml"))
|
||||||
|
g.Expect(err).To(BeNil())
|
||||||
|
g.Expect(string(content)).To(Equal("apiVersion: v1\nkind: ConfigMap\n"))
|
||||||
|
|
||||||
|
// Verify the symlinked file was resolved and copied
|
||||||
|
content, err = os.ReadFile(filepath.Join(resolved, "linked.yaml"))
|
||||||
|
g.Expect(err).To(BeNil())
|
||||||
|
g.Expect(string(content)).To(ContainSubstring("kind: Namespace"))
|
||||||
|
|
||||||
|
// Verify that the resolved file is a regular file, not a symlink
|
||||||
|
info, err := os.Lstat(filepath.Join(resolved, "linked.yaml"))
|
||||||
|
g.Expect(err).To(BeNil())
|
||||||
|
g.Expect(info.Mode().IsRegular()).To(BeTrue())
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -103,17 +103,18 @@ The command can read the credentials from '~/.docker/config.json' but they can a
|
||||||
}
|
}
|
||||||
|
|
||||||
type pushArtifactFlags struct {
|
type pushArtifactFlags struct {
|
||||||
path string
|
path string
|
||||||
source string
|
source string
|
||||||
revision string
|
revision string
|
||||||
creds string
|
creds string
|
||||||
provider flags.SourceOCIProvider
|
provider flags.SourceOCIProvider
|
||||||
ignorePaths []string
|
ignorePaths []string
|
||||||
annotations []string
|
annotations []string
|
||||||
output string
|
output string
|
||||||
debug bool
|
debug bool
|
||||||
reproducible bool
|
reproducible bool
|
||||||
insecure bool
|
insecure bool
|
||||||
|
resolveSymlinks bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var pushArtifactArgs = newPushArtifactFlags()
|
var pushArtifactArgs = newPushArtifactFlags()
|
||||||
|
|
@ -137,6 +138,7 @@ func init() {
|
||||||
pushArtifactCmd.Flags().BoolVarP(&pushArtifactArgs.debug, "debug", "", false, "display logs from underlying library")
|
pushArtifactCmd.Flags().BoolVarP(&pushArtifactArgs.debug, "debug", "", false, "display logs from underlying library")
|
||||||
pushArtifactCmd.Flags().BoolVar(&pushArtifactArgs.reproducible, "reproducible", false, "ensure reproducible image digests by setting the created timestamp to '1970-01-01T00:00:00Z'")
|
pushArtifactCmd.Flags().BoolVar(&pushArtifactArgs.reproducible, "reproducible", false, "ensure reproducible image digests by setting the created timestamp to '1970-01-01T00:00:00Z'")
|
||||||
pushArtifactCmd.Flags().BoolVar(&pushArtifactArgs.insecure, "insecure-registry", false, "allows artifacts to be pushed without TLS")
|
pushArtifactCmd.Flags().BoolVar(&pushArtifactArgs.insecure, "insecure-registry", false, "allows artifacts to be pushed without TLS")
|
||||||
|
pushArtifactCmd.Flags().BoolVar(&pushArtifactArgs.resolveSymlinks, "resolve-symlinks", false, "resolve symlinks by copying their targets into the artifact")
|
||||||
|
|
||||||
pushCmd.AddCommand(pushArtifactCmd)
|
pushCmd.AddCommand(pushArtifactCmd)
|
||||||
}
|
}
|
||||||
|
|
@ -183,6 +185,15 @@ func pushArtifactCmdRun(cmd *cobra.Command, args []string) error {
|
||||||
return fmt.Errorf("invalid path '%s', must point to an existing directory or file: %w", path, err)
|
return fmt.Errorf("invalid path '%s', must point to an existing directory or file: %w", path, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if pushArtifactArgs.resolveSymlinks {
|
||||||
|
resolved, err := resolveSymlinks(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("resolving symlinks failed: %w", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(resolved)
|
||||||
|
path = resolved
|
||||||
|
}
|
||||||
|
|
||||||
annotations := map[string]string{}
|
annotations := map[string]string{}
|
||||||
for _, annotation := range pushArtifactArgs.annotations {
|
for _, annotation := range pushArtifactArgs.annotations {
|
||||||
kv := strings.Split(annotation, "=")
|
kv := strings.Split(annotation, "=")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue