Borg/pkg/tim/tim.go
google-labs-jules[bot] 38fafbf639 feat: Add comprehensive docstrings and refactor matrix to tim
Add comprehensive Go docstrings with examples to all packages to achieve 100% coverage.

Refactor the `matrix` package to `tim` (Terminal Isolation Matrix). Update all references to the old package and terminology across the codebase, including commands, tests, and examples.

Fix inconsistencies in command-line flags and help text related to the refactoring.
2025-11-14 21:23:11 +00:00

183 lines
3.8 KiB
Go

// Package tim provides types and functions for creating and manipulating
// Terminal Isolation Matrix (.tim) files, which are runc-compatible container
// bundles.
package tim
import (
"archive/tar"
"bytes"
"encoding/json"
"errors"
"io/fs"
"github.com/Snider/Borg/pkg/datanode"
)
var (
// ErrDataNodeRequired is returned when a DataNode is required but not provided.
ErrDataNodeRequired = errors.New("datanode is required")
// ErrConfigIsNil is returned when the config is nil.
ErrConfigIsNil = errors.New("config is nil")
)
// TIM represents a runc-compatible container bundle. It consists of a runc
// configuration file (config.json) and a root filesystem (rootfs).
type TIM struct {
// Config is the JSON representation of the runc configuration.
Config []byte
// RootFS is an in-memory filesystem representing the container's root.
RootFS *datanode.DataNode
}
// New creates a new, empty TIM with a default runc
// configuration.
//
// Example:
//
// m, err := tim.New()
// if err != nil {
// // handle error
// }
// m.RootFS.AddData("hello.txt", []byte("hello world"))
func New() (*TIM, error) {
// Use the default runc spec as a starting point.
// This can be customized later.
spec, err := defaultConfigVar()
if err != nil {
return nil, err
}
specBytes, err := json.Marshal(spec)
if err != nil {
return nil, err
}
return &TIM{
Config: specBytes,
RootFS: datanode.New(),
}, nil
}
// FromDataNode creates a new TIM using the provided DataNode
// as the root filesystem. It uses a default runc configuration.
//
// Example:
//
// dn := datanode.New()
// dn.AddData("my-file.txt", []byte("hello"))
// m, err := tim.FromDataNode(dn)
// if err != nil {
// // handle error
// }
func FromDataNode(dn *datanode.DataNode) (*TIM, error) {
if dn == nil {
return nil, ErrDataNodeRequired
}
m, err := New()
if err != nil {
return nil, err
}
m.RootFS = dn
return m, nil
}
// ToTar serializes the TIM into a tar archive. The resulting
// tarball will contain a config.json file and a rootfs directory, making it
// compatible with runc.
//
// Example:
//
// // Assuming 'm' is a *tim.TIM
// tarData, err := m.ToTar()
// if err != nil {
// // handle error
// }
// err = os.WriteFile("my-bundle.tar", tarData, 0644)
// if err != nil {
// // handle error
// }
func (m *TIM) ToTar() ([]byte, error) {
if m.Config == nil {
return nil, ErrConfigIsNil
}
buf := new(bytes.Buffer)
tw := tar.NewWriter(buf)
// Add the config.json file.
hdr := &tar.Header{
Name: "config.json",
Mode: 0600,
Size: int64(len(m.Config)),
}
if err := tw.WriteHeader(hdr); err != nil {
return nil, err
}
if _, err := tw.Write(m.Config); err != nil {
return nil, err
}
// Add the rootfs directory.
hdr = &tar.Header{
Name: "rootfs/",
Mode: 0755,
Typeflag: tar.TypeDir,
}
if err := tw.WriteHeader(hdr); err != nil {
return nil, err
}
// Add the rootfs files.
err := m.RootFS.Walk(".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
// If the root directory doesn't exist (i.e. empty datanode), it's not an error.
if path == "." && errors.Is(err, fs.ErrNotExist) {
return nil
}
return err
}
if d.IsDir() {
return nil
}
file, err := m.RootFS.Open(path)
if err != nil {
return err
}
defer file.Close()
info, err := file.Stat()
if err != nil {
return err
}
hdr := &tar.Header{
Name: "rootfs/" + path,
Mode: 0600,
Size: info.Size(),
}
if err := tw.WriteHeader(hdr); err != nil {
return err
}
buf := new(bytes.Buffer)
if _, err := buf.ReadFrom(file); err != nil {
return err
}
if _, err := tw.Write(buf.Bytes()); err != nil {
return err
}
return nil
})
if err != nil {
return nil, err
}
if err := tw.Close(); err != nil {
return nil, err
}
return buf.Bytes(), nil
}