mirror of
https://github.com/coder/terraform-provider-envbuilder.git
synced 2025-07-25 21:17:51 +00:00
* fix: set MagicDir to tempdir when performing cache probe * chore: update envbuilder to b7781d8 * imgutil: get default envbuilder binary path from envbuilder options
106 lines
2.8 KiB
Go
106 lines
2.8 KiB
Go
package imgutil
|
|
|
|
import (
|
|
"archive/tar"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
eboptions "github.com/coder/envbuilder/options"
|
|
"github.com/google/go-containerregistry/pkg/authn"
|
|
"github.com/google/go-containerregistry/pkg/name"
|
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
|
"github.com/google/go-containerregistry/pkg/v1/remote"
|
|
"github.com/hashicorp/terraform-plugin-log/tflog"
|
|
)
|
|
|
|
// GetRemoteImage fetches the image manifest of the image.
|
|
func GetRemoteImage(imgRef string) (v1.Image, error) {
|
|
ref, err := name.ParseReference(imgRef)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parse reference: %w", err)
|
|
}
|
|
|
|
img, err := remote.Image(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("check remote image: %w", err)
|
|
}
|
|
|
|
return img, nil
|
|
}
|
|
|
|
// ExtractEnvbuilderFromImage reads the image located at imgRef and extracts
|
|
// MagicBinaryLocation to destPath.
|
|
func ExtractEnvbuilderFromImage(ctx context.Context, imgRef, destPath string) error {
|
|
var o eboptions.Options
|
|
o.SetDefaults()
|
|
needle := strings.TrimPrefix(o.BinaryPath, "/")
|
|
img, err := GetRemoteImage(imgRef)
|
|
if err != nil {
|
|
return fmt.Errorf("check remote image: %w", err)
|
|
}
|
|
|
|
layers, err := img.Layers()
|
|
if err != nil {
|
|
return fmt.Errorf("get image layers: %w", err)
|
|
}
|
|
|
|
// Check the layers in reverse order. The last layers are more likely to
|
|
// include the binary.
|
|
for i := len(layers) - 1; i >= 0; i-- {
|
|
ul, err := layers[i].Uncompressed()
|
|
if err != nil {
|
|
return fmt.Errorf("get uncompressed layer: %w", err)
|
|
}
|
|
|
|
tr := tar.NewReader(ul)
|
|
for {
|
|
th, err := tr.Next()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("read tar header: %w", err)
|
|
}
|
|
|
|
name := filepath.Clean(th.Name)
|
|
if th.Typeflag != tar.TypeReg {
|
|
tflog.Debug(ctx, "skip non-regular file", map[string]any{"name": name, "layer_idx": i + 1})
|
|
continue
|
|
}
|
|
|
|
if name != needle {
|
|
tflog.Debug(ctx, "skip file", map[string]any{"name": name, "layer_idx": i + 1})
|
|
continue
|
|
}
|
|
|
|
tflog.Debug(ctx, "found file", map[string]any{"name": name, "layer_idx": i + 1})
|
|
if err := os.MkdirAll(filepath.Dir(destPath), 0o755); err != nil {
|
|
return fmt.Errorf("create parent directories: %w", err)
|
|
}
|
|
destF, err := os.Create(destPath)
|
|
if err != nil {
|
|
return fmt.Errorf("create dest file for writing: %w", err)
|
|
}
|
|
defer destF.Close()
|
|
_, err = io.Copy(destF, tr)
|
|
if err != nil {
|
|
return fmt.Errorf("copy dest file from image: %w", err)
|
|
}
|
|
if err := destF.Close(); err != nil {
|
|
return fmt.Errorf("close dest file: %w", err)
|
|
}
|
|
|
|
if err := os.Chmod(destPath, 0o755); err != nil {
|
|
return fmt.Errorf("chmod file: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return fmt.Errorf("extract envbuilder binary from image %q: %w", imgRef, os.ErrNotExist)
|
|
}
|