package main import ( "flag" "io/fs" "log" "os" "path/filepath" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" yaml "gopkg.in/yaml.v2" "novit.tech/direktil/pkg/localconfig" "novit.tech/direktil/local-server/pkg/clustersconfig" ) var ( Debug = false dir = flag.String("in", ".", "Source directory") outPath = flag.String("out", "config.yaml", "Output file") base fs.FS src *clustersconfig.Config dst *localconfig.Config ) func init() { flag.BoolVar(&Debug, "debug", Debug, "debug") } func loadSrc() { var err error src, err = clustersconfig.FromDir(read, assemble, listBase, listMerged) if err != nil { log.Fatal("failed to load config from dir: ", err) } } func main() { flag.Parse() log.SetFlags(log.Ltime | log.Lmicroseconds | log.Lshortfile) base = os.DirFS(*dir) searchList = append(searchList, fsFS{base}) openIncludes() if false { assemble("hosts/m1") log.Fatal("--- debug: end ---") } loadSrc() dst = &localconfig.Config{ SSLConfig: src.SSLConfig, } // ---------------------------------------------------------------------- for _, cluster := range src.Clusters { dst.Clusters = append(dst.Clusters, &localconfig.Cluster{ Name: cluster.Name, Addons: renderAddons(cluster), }) } // ---------------------------------------------------------------------- for _, host := range src.Hosts { log.Print("rendering host ", host.Name) ctx, err := newRenderContext(host, src) if err != nil { log.Fatal("failed to create render context for host ", host.Name, ": ", err) } macs := make([]string, 0) if host.MAC != "" { macs = append(macs, host.MAC) } ips := make([]string, 0) if len(host.IP) != 0 { ips = append(ips, host.IP) } ips = append(ips, host.IPs...) if ctx.Host.Versions["modules"] == "" { // default modules' version to kernel's version ctx.Host.Versions["modules"] = ctx.Host.Kernel } dst.Hosts = append(dst.Hosts, &localconfig.Host{ Name: host.Name, ClusterName: ctx.Cluster.Name, Labels: ctx.Labels, Annotations: ctx.Annotations, MACs: macs, IPs: ips, IPXE: ctx.Host.IPXE, // TODO render Kernel: ctx.Host.Kernel, Initrd: ctx.Host.Initrd, Versions: ctx.Host.Versions, BootstrapConfig: ctx.BootstrapConfig(), Config: ctx.Config(), }) } // ---------------------------------------------------------------------- out, err := os.Create(*outPath) if err != nil { log.Fatal("failed to create output: ", err) } defer out.Close() if err = yaml.NewEncoder(out).Encode(dst); err != nil { log.Fatal("failed to render output: ", err) } } func cfgPath(subPath string) string { return filepath.Join(*dir, subPath) } func openIncludes() { includesFile, err := base.Open("includes.yaml") if os.IsNotExist(err) { return } if err != nil { log.Fatal("failed to open includes: ", err) } includes := make([]struct { Path string Branch string Tag string }, 0) err = yaml.NewDecoder(includesFile).Decode(&includes) if err != nil { log.Fatal("failed to parse includes: ", err) } for _, include := range includes { switch { case include.Branch != "" || include.Tag != "": p := cfgPath(include.Path) // FIXME parse git path to allow remote repos var rev plumbing.Revision switch { case include.Branch != "": log.Printf("opening include path %q as git, branch %q", p, include.Branch) rev = plumbing.Revision(plumbing.NewBranchReferenceName(include.Branch)) case include.Tag != "": log.Printf("opening include path %q as git, tag %q", p, include.Branch) rev = plumbing.Revision(plumbing.NewTagReferenceName(include.Branch)) } repo, err := git.PlainOpen(p) if err != nil { log.Fatal("failed to open: ", err) } revH, err := repo.ResolveRevision(rev) if err != nil { log.Fatalf("failed to resolve revision %s: %v", rev, err) } log.Print(" -> resolved to commit ", *revH) commit, err := repo.CommitObject(*revH) if err != nil { log.Fatal("failed to get commit object: ", err) } tree, err := commit.Tree() if err != nil { log.Fatal("failed to open git tree: ", err) } searchList = append(searchList, gitFS{tree}) default: p := cfgPath(include.Path) log.Printf("opening include path %q as raw dir", p) searchList = append(searchList, fsFS{os.DirFS(p)}) } } }