Skip to content
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

bugfix: adding files from another path that running the test from #92

Merged
merged 1 commit into from
Jun 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 17 additions & 15 deletions pkg/container/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/google/uuid"
"github.com/sirupsen/logrus"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
)
Expand All @@ -24,10 +24,11 @@ type BuilderFactory struct {
imageNameTo string
cli *client.Client
dockerFileInstructions []string
context string
}

// NewBuilderFactory creates a new instance of BuilderFactory.
func NewBuilderFactory(imageName string) (*BuilderFactory, error) {
func NewBuilderFactory(imageName string, buildContext string) (*BuilderFactory, error) {
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
return nil, fmt.Errorf("failed to create docker client: %w", err)
Expand All @@ -36,6 +37,7 @@ func NewBuilderFactory(imageName string) (*BuilderFactory, error) {
imageNameFrom: imageName,
cli: cli,
dockerFileInstructions: []string{"FROM " + imageName},
context: buildContext,
}, nil
}

Expand Down Expand Up @@ -158,18 +160,18 @@ func (f *BuilderFactory) PushBuilderImage(imageName string) error {
return nil
}

// Generate a UUID
uuid, err := uuid.NewRandom()
if err != nil {
return fmt.Errorf("failed to generate UUID: %w", err)
}

f.imageNameTo = imageName

// Create a Dockerfile with a unique name
dockerFileName := fmt.Sprintf("Dockerfile_%s", uuid.String())
dockerFilePath := filepath.Join(f.context, "Dockerfile")
// create path if it does not exist
if _, err := os.Stat(f.context); os.IsNotExist(err) {
err = os.MkdirAll(f.context, 0755)
if err != nil {
return fmt.Errorf("failed to create context directory: %w", err)
}
}
dockerFile := strings.Join(f.dockerFileInstructions, "\n")
err = os.WriteFile(dockerFileName, []byte(dockerFile), 0644)
err := os.WriteFile(dockerFilePath, []byte(dockerFile), 0644)
if err != nil {
return fmt.Errorf("failed to write Dockerfile: %w", err)
}
Expand All @@ -191,7 +193,7 @@ func (f *BuilderFactory) PushBuilderImage(imageName string) error {
}

// Build the Docker image using buildx
cmd = exec.Command("docker", "buildx", "build", "--load", "--platform", "linux/amd64", "-t", imageName, ".", "-f", dockerFileName)
cmd = exec.Command("docker", "buildx", "build", "--load", "--platform", "linux/amd64", "-t", imageName, f.context)
err = runCommand(cmd)
if err != nil {
return fmt.Errorf("failed to build image: %w", err)
Expand All @@ -204,10 +206,10 @@ func (f *BuilderFactory) PushBuilderImage(imageName string) error {
return fmt.Errorf("failed to push image: %w", err)
}

// Remove the Dockerfile
err = os.Remove(dockerFileName)
// Remove the context directory
err = os.RemoveAll(f.context)
if err != nil {
return fmt.Errorf("failed to remove Dockerfile: %w", err)
return fmt.Errorf("failed to remove context directory: %w", err)
}

return nil
Expand Down
113 changes: 90 additions & 23 deletions pkg/knuu/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import (
"fmt"
"github.com/celestiaorg/knuu/pkg/container"
"github.com/celestiaorg/knuu/pkg/k8s"
"github.com/google/uuid"
"github.com/sirupsen/logrus"
"io"
v1 "k8s.io/api/core/v1"
"os"
"path/filepath"
Expand Down Expand Up @@ -76,7 +76,7 @@ func (i *Instance) SetImage(image string) error {
switch i.state {
case None:
// Use the builder to build a new image
factory, err := container.NewBuilderFactory(image)
factory, err := container.NewBuilderFactory(image, i.getBuildDir())
//builder, storage, err := container.NewBuilder(context, image)
if err != nil {
return fmt.Errorf("error creating builder: %s", err.Error())
Expand Down Expand Up @@ -249,20 +249,92 @@ func (i *Instance) AddFile(src string, dest string, chown string) error {
return fmt.Errorf("adding file is only allowed in state 'Preparing'. Current state is '%s'", i.state.String())
}

i.files = append(i.files, dest)
err := i.builderFactory.AddToBuilder(src, dest, chown)
i.validateFileArgs(src, dest, chown)

// check if src exists (either as file or as folder)
if _, err := os.Stat(src); os.IsNotExist(err) {
return fmt.Errorf("src '%s' does not exist", src)
}

// copy file to build dir
dstPath := filepath.Join(i.getBuildDir(), dest)

// make sure dir exists
err := os.MkdirAll(filepath.Dir(dstPath), os.ModePerm)
if err != nil {
return fmt.Errorf("error creating directory: %w", err)
}
// Create destination file making sure the path is writeable.
dst, err := os.Create(dstPath)
if err != nil {
return fmt.Errorf("failed to create destination file '%s': %w", dstPath, err)
}
defer dst.Close()

// Open source file for reading.
srcFile, err := os.Open(src)
if err != nil {
return fmt.Errorf("error adding file '%s' to instance '%s': %w", dest, i.name, err)
return fmt.Errorf("failed to open source file '%s': %w", src, err)
}
defer srcFile.Close()

// Copy the contents from source file to destination file
_, err = io.Copy(dst, srcFile)
if err != nil {
return fmt.Errorf("failed to copy from source '%s' to destination '%s': %w", src, dstPath, err)
}

i.addFileToBuilder(src, dest, chown)

logrus.Debugf("Added file '%s' to instance '%s'", dest, i.name)
return nil
}

// AddFolder adds a folder to the instance
// This function can only be called in the state 'Preparing'
func (i *Instance) AddFolder(src string, dest string, chown string) error {
// As dockers `ADD` works for both files and folders, we can just call AddFile here
return i.AddFile(src, dest, chown)
if !i.IsInState(Preparing) {
return fmt.Errorf("adding folder is only allowed in state 'Preparing'. Current state is '%s'", i.state.String())
}

i.validateFileArgs(src, dest, chown)

// check if src exists (should be a folder)
srcInfo, err := os.Stat(src)
if os.IsNotExist(err) || !srcInfo.IsDir() {
return fmt.Errorf("src '%s' does not exist or is not a directory", src)
}

// iterate over the files/directories in the src
err = filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}

// create the destination path
relPath, err := filepath.Rel(src, path)
if err != nil {
return err
}
dstPath := filepath.Join(i.getBuildDir(), dest, relPath)

if info.IsDir() {
// create directory at destination path
return os.MkdirAll(dstPath, os.ModePerm)
} else {
// copy file to destination path
return i.AddFile(path, filepath.Join(dest, relPath), chown)
}

return nil
})

if err != nil {
return fmt.Errorf("error copying folder '%s' to instance '%s': %w", src, i.name, err)
}

logrus.Debugf("Added folder '%s' to instance '%s'", dest, i.name)
return nil
}

// AddFileBytes adds a file with the given content to the instance
Expand All @@ -272,28 +344,23 @@ func (i *Instance) AddFileBytes(bytes []byte, dest string, chown string) error {
return fmt.Errorf("adding file is only allowed in state 'Preparing'. Current state is '%s'", i.state.String())
}

uuid, err := uuid.NewRandom()
// create a temporary file
tmpfile, err := os.CreateTemp("", "temp")
if err != nil {
return fmt.Errorf("error creating uuid: %w", err)
return err
}
file := "./tmp/" + uuid.String() + "/" + dest
filePath := filepath.Dir(file)
defer os.Remove(tmpfile.Name()) // clean up

// write to a file in the ./<uuid> directory, make sure dir exists
err = os.MkdirAll(filePath, os.ModePerm)
if err != nil {
return fmt.Errorf("error creating directory: %w", err)
// write bytes to the temporary file
if _, err := tmpfile.Write(bytes); err != nil {
return err
}

// write to a file in the ./<uuid> directory
err = os.WriteFile(file, bytes, 0644)
if err != nil {
return fmt.Errorf("error writing file: %w", err)
if err := tmpfile.Close(); err != nil {
return err
}

i.AddFile(file, dest, chown)

return nil
// use AddFile to copy the temp file to the destination
return i.AddFile(tmpfile.Name(), dest, chown)
}

// SetUser sets the user for the instance
Expand Down
40 changes: 40 additions & 0 deletions pkg/knuu/instance_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/api/resource"
"net"
"path/filepath"
"strings"
)

// getImageRegistry returns the name of the temporary image registry
Expand Down Expand Up @@ -230,3 +232,41 @@ func getFreePortTCP() (int, error) {

return port, nil
}

// getBuildDir returns the build directory for the instance
func (i *Instance) getBuildDir() string {
return filepath.Join("/tmp", "knuu", i.k8sName)
}

// validateFileArgs validates the file arguments
func (i *Instance) validateFileArgs(src string, dest string, chown string) error {
// check src
if src == "" {
return fmt.Errorf("src must be set")
}
// check dest
if dest == "" {
return fmt.Errorf("dest must be set")
}
// check chown
if chown == "" {
return fmt.Errorf("chown must be set")
}
// validate chown format
if !strings.Contains(chown, ":") || len(strings.Split(chown, ":")) != 2 {
return fmt.Errorf("chown must be in format 'user:group'")
}

return nil
}

// addFileToBuilder adds a file to the builder
func (i *Instance) addFileToBuilder(src string, dest string, chown string) error {
i.files = append(i.files, dest)
// dest is the same as src here, as we copy the file to the build dir with the subfolder structure of dest
err := i.builderFactory.AddToBuilder(dest, dest, chown)
if err != nil {
return fmt.Errorf("error adding file '%s' to instance '%s': %w", dest, i.name, err)
}
return nil
}