aboutsummaryrefslogblamecommitdiff
path: root/nix2/nix.go
blob: 7a86934e59a3d7f8591128d752482e13aaac5b94 (plain) (tree)



































































































































































                                                                                                         
package nix2

import (
	"bytes"
	"path/filepath"
	"encoding/json"
	"fmt"
	"os"
	"os/exec"

	"github.com/hashicorp/nomad/helper/pluginutils/hclutils"
)

const (
	closureNix = `
{ path }:
let
  nixpkgs = builtins.getFlake "github:nixos/nixpkgs/nixos-22.05";
  inherit (nixpkgs.legacyPackages.x86_64-linux) buildPackages;
in buildPackages.closureInfo { rootPaths = builtins.storePath path; }
`
)

func prepareNixPackages(taskDir string, packages []string) (hclutils.MapStrStr, error) {
	mounts := make(hclutils.MapStrStr)

	profileLink := filepath.Join(taskDir, "current-profile")
	profile, err := nixBuildProfile(packages, profileLink)
	if err != nil {
		return nil, fmt.Errorf("Build of the flakes failed: %v", err)
	}

	closureLink := filepath.Join(taskDir, "current-closure")
	closure, err := nixBuildClosure(profileLink, closureLink)
	if err != nil {
		return nil, fmt.Errorf("Build of the flakes failed: %v", err)
	}

	mounts[profile] = profile

	if entries, err := os.ReadDir(profile); err != nil {
		return nil, fmt.Errorf("Couldn't read profile directory: %w", err)
	} else {
		for _, entry := range entries {
			if name := entry.Name(); name != "etc" {
				mounts[filepath.Join(profile, name)] = "/" + name
				continue
			}

			etcEntries, err := os.ReadDir(filepath.Join(profile, "etc"))
			if err != nil {
				return nil, fmt.Errorf("Couldn't read profile's /etc directory: %w", err)
			}

			for _, etcEntry := range etcEntries {
				etcName := etcEntry.Name()
				mounts[filepath.Join(profile, "etc", etcName)] = "/etc/" + etcName
			}
		}
	}

	mounts[filepath.Join(closure, "registration")] = "/registration"

	requisites, err := nixRequisites(closure)
	if err != nil {
		return nil, fmt.Errorf("Couldn't determine flake requisites: %v", err)
	}

	for _, requisite := range requisites {
		mounts[requisite] = requisite
	}

	return mounts, nil
}

func nixBuildProfile(flakes []string, link string) (string, error) {
	cmd := exec.Command("nix", append(
		[]string{
			"--extra-experimental-features", "nix-command",
			"--extra-experimental-features", "flakes",
			"profile",
			"install",
			"--no-write-lock-file",
			"--profile",
			link},
		flakes...)...)
	stderr := &bytes.Buffer{}
	cmd.Stderr = stderr

	if err := cmd.Run(); err != nil {
		return "", fmt.Errorf("%v failed: %s. Err: %v", cmd.Args, stderr.String(), err)
	}

	if target, err := os.Readlink(link); err == nil {
		return os.Readlink(filepath.Join(filepath.Dir(link), target))
	} else {
		return "", err
	}
}

func nixBuildClosure(profile string, link string) (string, error) {
	cmd := exec.Command(
		"nix",
		"--extra-experimental-features", "nix-command",
		"--extra-experimental-features", "flakes",
		"build",
		"--out-link", link,
		"--expr", closureNix,
		"--impure",
		"--no-write-lock-file",
		"--argstr", "path", profile)

	stderr := &bytes.Buffer{}
	cmd.Stderr = stderr

	if err := cmd.Run(); err != nil {
		return "", fmt.Errorf("%v failed: %s. Err: %v", cmd.Args, stderr.String(), err)
	}

	return os.Readlink(link)
}

type nixPathInfo struct {
	Path             string   `json:"path"`
	NarHash          string   `json:"narHash"`
	NarSize          uint64   `json:"narSize"`
	References       []string `json:"references"`
	Deriver          string   `json:"deriver"`
	RegistrationTime uint64   `json:"registrationTime"`
	Signatures       []string `json:"signatures"`
}

func nixRequisites(path string) ([]string, error) {
	cmd := exec.Command(
		"nix",
		"--extra-experimental-features", "nix-command",
		"--extra-experimental-features", "flakes",
		"path-info",
		"--json",
		"--recursive",
		path)

	stdout := &bytes.Buffer{}
	cmd.Stdout = stdout

	stderr := &bytes.Buffer{}
	cmd.Stderr = stderr

	if err := cmd.Run(); err != nil {
		return nil, fmt.Errorf("%v failed: %s. Err: %v", cmd.Args, stderr.String(), err)
	}

	result := []*nixPathInfo{}
	if err := json.Unmarshal(stdout.Bytes(), &result); err != nil {
		return nil, err
	}

	requisites := []string{}
	for _, result := range result {
		requisites = append(requisites, result.Path)
	}

	return requisites, nil
}