// Copyright 2022 The TVL Contributors // SPDX-License-Identifier: Apache-2.0 package config import ( "crypto/sha1" "encoding/json" "fmt" "os" "regexp" "strings" log "github.com/sirupsen/logrus" ) // PkgSource represents the source from which the Nix package set used // by Nixery is imported. Users configure the source by setting one of // the supported environment variables. type PkgSource interface { // Convert the package source into the representation required // for calling Nix. Render(tag string) (string, string) // Create a key by which builds for this source and image // combination can be cached. // // The empty string means that this value is not cacheable due // to the package source being a moving target (such as a // channel). CacheKey(pkgs []string, tag string) string } type GitSource struct { repository string } // Regex to determine whether a git reference is a commit hash or // something else (branch/tag). // // Used to check whether a git reference is cacheable, and to pass the // correct git structure to Nix. // // Note: If a user creates a branch or tag with the name of a commit // and references it intentionally, this heuristic will fail. var commitRegex = regexp.MustCompile(`^[0-9a-f]{40}$`) func (g *GitSource) Render(tag string) (string, string) { args := map[string]string{ "url": g.repository, } // The 'git' source requires a tag to be present. If the user // has not specified one, it is assumed that the default // 'master' branch should be used. if tag == "latest" || tag == "" { tag = "master" } if commitRegex.MatchString(tag) { args["rev"] = tag } else { args["ref"] = tag } j, _ := json.Marshal(args) return "git", string(j) } func (g *GitSource) CacheKey(pkgs []string, tag string) string { // Only full commit hashes can be used for caching, as // everything else is potentially a moving target. if !commitRegex.MatchString(tag) { return "" } unhashed := strings.Join(pkgs, "") + tag hashed := fmt.Sprintf("%x", sha1.Sum([]byte(unhashed))) return hashed } type NixChannel struct { channel string } func (n *NixChannel) Render(tag string) (string, string) { return "nixpkgs", n.channel } func (n *NixChannel) CacheKey(pkgs []string, tag string) string { // Since Nix channels are downloaded from the nixpkgs-channels // Github, users can specify full commit hashes as the // "channel", in which case builds are cacheable. if !commitRegex.MatchString(n.channel) { return "" } unhashed := strings.Join(pkgs, "") + n.channel hashed := fmt.Sprintf("%x", sha1.Sum([]byte(unhashed))) return hashed } type PkgsPath struct { path string } func (p *PkgsPath) Render(tag string) (string, string) { return "path", p.path } func (p *PkgsPath) CacheKey(pkgs []string, tag string) string { // Path-based builds are not currently cacheable because we // have no local hash of the package folder's state easily // available. return "" } // Retrieve a package source from the environment. If no source is // specified, the Nix code will default to a recent NixOS channel. func pkgSourceFromEnv() (PkgSource, error) { if channel := os.Getenv("NIXERY_CHANNEL"); channel != "" { log.WithField("channel", channel).Info("using Nix package set from Nix channel or commit") return &NixChannel{ channel: channel, }, nil } if git := os.Getenv("NIXERY_PKGS_REPO"); git != "" { log.WithField("repo", git).Info("using Nix package set from git repository") return &GitSource{ repository: git, }, nil } if path := os.Getenv("NIXERY_PKGS_PATH"); path != "" { log.WithField("path", path).Info("using Nix package set at local path") return &PkgsPath{ path: path, }, nil } return nil, fmt.Errorf("no valid package source has been specified") }