// Package bootstrap deals with the creation of bootstrap files package bootstrap import ( "cryptic-net/garage" "cryptic-net/tarutil" "cryptic-net/yamlutil" "fmt" "io" "io/fs" "os" "strings" ) // Paths within the bootstrap FS which for general data. const ( HostNamePath = "hostname" ) // Bootstrap is used for accessing all information contained within a // bootstrap.tgz file. // // An instance of Bootstrap is read-only, the creator sub-package should be used // to create new instances. type Bootstrap struct { Hosts map[string]Host HostName string NebulaCertsCACert string NebulaCertsHostCert string NebulaCertsHostKey string GarageRPCSecret string GarageGlobalBucketS3APICredentials garage.S3APICredentials // Hash is a determinstic hash of the contents of the bootstrap file. This // will be populated when parsing a Bootstrap from a bootstrap.tgz, but will // be ignored when creating a new bootstrap.tgz. Hash []byte // DEPRECATED do not use FS fs.FS } // FromFS loads a Boostrap instance from the given fs.FS, which presumably // represents the file structure of a bootstrap.tgz file. func FromFS(bootstrapFS fs.FS) (Bootstrap, error) { var ( b Bootstrap err error ) b.FS = bootstrapFS if b.Hosts, err = loadHosts(bootstrapFS); err != nil { return Bootstrap{}, fmt.Errorf("loading hosts info from fs: %w", err) } if err = yamlutil.LoadYamlFSFile( &b.GarageGlobalBucketS3APICredentials, bootstrapFS, GarageGlobalBucketKeyYmlPath, ); err != nil { return Bootstrap{}, fmt.Errorf("loading %q from fs: %w", b.GarageGlobalBucketS3APICredentials, err) } filesToLoadAsString := []struct { into *string path string }{ {&b.HostName, HostNamePath}, {&b.NebulaCertsCACert, NebulaCertsCACertPath}, {&b.NebulaCertsHostCert, NebulaCertsHostCertPath}, {&b.NebulaCertsHostKey, NebulaCertsHostKeyPath}, {&b.GarageRPCSecret, GarageRPCSecretPath}, } for _, f := range filesToLoadAsString { body, err := fs.ReadFile(bootstrapFS, f.path) if err != nil { return Bootstrap{}, fmt.Errorf("loading %q from fs: %w", f.path, err) } *f.into = string(body) } // TODO confirm if this is necessary b.GarageRPCSecret = strings.TrimSpace(b.GarageRPCSecret) if b.Hash, err = fs.ReadFile(bootstrapFS, tarutil.HashBinPath); err != nil { return Bootstrap{}, fmt.Errorf("loading %q from fs: %w", tarutil.HashBinPath, err) } return b, nil } // FromReader reads a bootstrap.tgz file from the given io.Reader. func FromReader(r io.Reader) (Bootstrap, error) { fs, err := tarutil.FSFromReader(r) if err != nil { return Bootstrap{}, fmt.Errorf("reading bootstrap.tgz: %w", err) } return FromFS(fs) } // FromFile reads a bootstrap.tgz from a file at the given path. func FromFile(path string) (Bootstrap, error) { f, err := os.Open(path) if err != nil { return Bootstrap{}, fmt.Errorf("opening file: %w", err) } defer f.Close() return FromReader(f) } // ThisHost is a shortcut for b.Hosts[b.HostName], but will panic if the // HostName isn't found in the Hosts map. func (b Bootstrap) ThisHost() Host { host, ok := b.Hosts[b.HostName] if !ok { panic(fmt.Sprintf("hostname %q not defined in bootstrap's hosts", b.HostName)) } return host }