/*
FilesystemDeploy Action

Deploy prepared root filesystem to output image by copying the files from the
temporary scratch directory to the mounted image and optionally creates various
configuration files for the image: '/etc/fstab' and '/etc/kernel/cmdline'. This
action requires 'image-partition' action to be executed before it.

After this action has ran, subsequent actions are executed on the mounted output
image.

Yaml syntax:
 - action: filesystem-deploy
   setup-fstab: bool
   setup-kernel-cmdline: bool
   append-kernel-cmdline: arguments

Optional properties:

- setup-fstab -- generate '/etc/fstab' file according to information provided
by 'image-partition' action. By default is 'true'.

- setup-kernel-cmdline -- add location of root partition to '/etc/kernel/cmdline'
file on target image. By default is 'true'.

- append-kernel-cmdline -- additional kernel command line arguments passed to kernel.
*/
package actions

import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path"
	"strings"

	"github.com/go-debos/debos"
)

type FilesystemDeployAction struct {
	debos.BaseAction    `yaml:",inline"`
	SetupFSTab          bool   `yaml:"setup-fstab"`
	SetupKernelCmdline  bool   `yaml:"setup-kernel-cmdline"`
	AppendKernelCmdline string `yaml:"append-kernel-cmdline"`
}

func NewFilesystemDeployAction() *FilesystemDeployAction {
	fd := &FilesystemDeployAction{SetupFSTab: true, SetupKernelCmdline: true}
	fd.Description = "Deploying filesystem"

	return fd
}

func (fd *FilesystemDeployAction) setupFSTab(context *debos.DebosContext) error {
	if context.ImageFSTab.Len() == 0 {
		return errors.New("Fstab not generated, missing image-partition action?")
	}

	log.Print("Setting up fstab")

	err := os.MkdirAll(path.Join(context.Rootdir, "etc"), 0755)
	if err != nil {
		return fmt.Errorf("Couldn't create etc in image: %v", err)
	}

	fstab := path.Join(context.Rootdir, "etc/fstab")
	f, err := os.OpenFile(fstab, os.O_RDWR|os.O_CREATE, 0755)

	if err != nil {
		return fmt.Errorf("Couldn't open fstab: %v", err)
	}

	_, err = io.Copy(f, &context.ImageFSTab)

	if err != nil {
		return fmt.Errorf("Couldn't write fstab: %v", err)
	}
	f.Close()

	return nil
}

func (fd *FilesystemDeployAction) setupKernelCmdline(context *debos.DebosContext) error {
	var cmdline []string

	log.Print("Setting up /etc/kernel/cmdline")

	err := os.MkdirAll(path.Join(context.Rootdir, "etc", "kernel"), 0755)
	if err != nil {
		return fmt.Errorf("Couldn't create etc/kernel in image: %v", err)
	}
	path := path.Join(context.Rootdir, "etc/kernel/cmdline")
	current, _ := ioutil.ReadFile(path)
	f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0755)

	if err != nil {
		log.Fatalf("Couldn't open kernel cmdline: %v", err)
	}

	cmdline = append(cmdline, strings.TrimSpace(string(current)))
	cmdline = append(cmdline, context.ImageKernelRoot)

	if fd.AppendKernelCmdline != "" {
		cmdline = append(cmdline, fd.AppendKernelCmdline)
	}

	_, err = f.WriteString(strings.Join(cmdline, " ") + "\n")
	if err != nil {
		return fmt.Errorf("Couldn't write kernel/cmdline: %v", err)
	}

	f.Close()
	return nil
}

func (fd *FilesystemDeployAction) Run(context *debos.DebosContext) error {
	fd.LogStart()
	/* Copying files is actually silly hafd, one has to keep permissions, ACL's
	 * extended attribute, misc, other. Leave it to cp...
	 */
	err := debos.Command{}.Run("Deploy to image", "cp", "-a", context.Rootdir+"/.", context.ImageMntDir)
	if err != nil {
		return fmt.Errorf("rootfs deploy failed: %v", err)
	}
	context.Rootdir = context.ImageMntDir
	context.Origins["filesystem"] = context.ImageMntDir

	if fd.SetupFSTab {
		err = fd.setupFSTab(context)
		if err != nil {
			return err
		}
	}
	if fd.SetupKernelCmdline {
		err = fd.setupKernelCmdline(context)
		if err != nil {
			return err
		}
	}

	return nil
}
