m/n/build: implement new fsspec infrastructure

This makes the node_initramfs and erofs_image use the new common fsspec
infrastructure. It also adds the fsspecs attribute to both which can
later be used to add arbitrary fsspecs.

Change-Id: I384e04712c0a70f82c5c975911cbb1d0d5e6cabc
Reviewed-on: https://review.monogon.dev/c/monogon/+/530
Reviewed-by: Sergiusz Bazanski <serge@monogon.tech>
diff --git a/metropolis/node/build/fsspec/BUILD.bazel b/metropolis/node/build/fsspec/BUILD.bazel
index 3a65d97..e0ce66d 100644
--- a/metropolis/node/build/fsspec/BUILD.bazel
+++ b/metropolis/node/build/fsspec/BUILD.bazel
@@ -10,9 +10,11 @@
 
 go_library(
     name = "go_default_library",
+    srcs = ["utils.go"],
     embed = [":fsspec_go_proto"],
     importpath = "source.monogon.dev/metropolis/node/build/fsspec",
     visibility = ["//visibility:public"],
+    deps = ["@com_github_golang_protobuf//proto:go_default_library"],
 )
 
 go_proto_library(
diff --git a/metropolis/node/build/fsspec/utils.go b/metropolis/node/build/fsspec/utils.go
new file mode 100644
index 0000000..2438220
--- /dev/null
+++ b/metropolis/node/build/fsspec/utils.go
@@ -0,0 +1,38 @@
+package fsspec
+
+import (
+	"fmt"
+	"os"
+
+	"github.com/golang/protobuf/proto"
+)
+
+// ReadMergeSpecs reads FSSpecs from all files in paths and merges them into
+// a single FSSpec.
+func ReadMergeSpecs(paths []string) (*FSSpec, error) {
+	var mergedSpec FSSpec
+	for _, p := range paths {
+		specRaw, err := os.ReadFile(p)
+		if err != nil {
+			return nil, fmt.Errorf("failed to open spec: %w", err)
+		}
+
+		var spec FSSpec
+		if err := proto.UnmarshalText(string(specRaw), &spec); err != nil {
+			return nil, fmt.Errorf("failed to parse spec %q: %w", p, err)
+		}
+		for _, f := range spec.File {
+			mergedSpec.File = append(mergedSpec.File, f)
+		}
+		for _, d := range spec.Directory {
+			mergedSpec.Directory = append(mergedSpec.Directory, d)
+		}
+		for _, s := range spec.SymbolicLink {
+			mergedSpec.SymbolicLink = append(mergedSpec.SymbolicLink, s)
+		}
+		for _, s := range spec.SpecialFile {
+			mergedSpec.SpecialFile = append(mergedSpec.SpecialFile, s)
+		}
+	}
+	return &mergedSpec, nil
+}