Files
threshold/internal/bsr/container.go
T
2023-05-31 12:18:13 +00:00

243 lines
6.5 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package bsr
import (
"context"
"errors"
"fmt"
"github.com/hashicorp/boundary/internal/bsr/internal/checksum"
"github.com/hashicorp/boundary/internal/bsr/internal/journal"
"github.com/hashicorp/boundary/internal/bsr/internal/sign"
"github.com/hashicorp/boundary/internal/bsr/kms"
"github.com/hashicorp/boundary/internal/storage"
)
const (
metaFile = "%s.meta"
summaryFile = "%s.summary"
checksumFile = "SHA256SUM"
sigFile = "SHA256SUM.sig"
journalFile = ".journal"
)
// ContainerType defines the type of container.
type containerType string
// Valid container types.
const (
sessionContainer containerType = "session"
connectionContainer containerType = "connection"
channelContainer containerType = "channel"
)
// container contains a group of files in a BSR.
// Each container has corresponding .meta, .summary, SHA256SUM, and SHA256SUM.sig files.
type container struct {
container storage.Container
journal *journal.Journal
metaName string
sumName string
meta *checksum.File
sum *checksum.File
checksums *sign.File
sigs storage.File
keys *kms.Keys
}
// newContainer creates a container for the given type backed by the provide storage.Container.
func newContainer(ctx context.Context, t containerType, c storage.Container, keys *kms.Keys) (*container, error) {
j, err := c.OpenFile(ctx, journalFile,
storage.WithCreateFile(),
storage.WithFileAccessMode(storage.WriteOnly),
storage.WithCloseSyncMode(storage.NoSync))
if err != nil {
return nil, err
}
jj, err := journal.New(ctx, j)
if err != nil {
return nil, err
}
cc := &container{
container: c,
journal: jj,
keys: keys,
}
cc.sigs, err = cc.create(ctx, sigFile)
if err != nil {
return nil, err
}
cs, err := cc.create(ctx, checksumFile)
if err != nil {
return nil, err
}
cc.checksums, err = sign.NewFile(ctx, cs, cc.sigs, keys)
if err != nil {
return nil, err
}
cc.metaName = fmt.Sprintf(metaFile, t)
meta, err := cc.create(ctx, cc.metaName)
if err != nil {
return nil, err
}
cc.meta, err = checksum.NewFile(ctx, meta, cc.checksums)
if err != nil {
return nil, err
}
cc.sumName = fmt.Sprintf(summaryFile, t)
sum, err := cc.create(ctx, cc.sumName)
if err != nil {
return nil, err
}
cc.sum, err = checksum.NewFile(ctx, sum, cc.checksums)
if err != nil {
return nil, err
}
return cc, nil
}
// create creates a new file in the container for writing.
func (c *container) create(ctx context.Context, s string, options ...storage.Option) (storage.File, error) {
const op = "bsr.(container).create"
err := c.journal.Record("CREATING", s)
if err != nil {
return nil, fmt.Errorf("%s: %w", op, err)
}
var f storage.File
switch len(options) {
case 0:
f, err = c.container.Create(ctx, s)
if err != nil {
return nil, fmt.Errorf("%s: %w", op, err)
}
default:
f, err = c.container.OpenFile(ctx, s, options...)
if err != nil {
return nil, fmt.Errorf("%s: %w", op, err)
}
}
jf, err := journal.NewFile(ctx, f, c.journal)
if err != nil {
return nil, fmt.Errorf("%s: %w", op, err)
}
defer c.journal.Record("CREATED", s)
return jf, nil
}
// syncBsrKey will take the marshalled bytes of a key, write its contents to a local file,
// and then close it. The key file is created using the synchronous storage option, so
// close will block until the file is synced to remote storage
func (c *container) syncBsrKey(ctx context.Context, s string, data []byte) error {
const op = "bsr.(container).syncBsrKey"
switch {
case len(s) == 0:
return fmt.Errorf("%s: missing file name %w", op, ErrInvalidParameter)
case data == nil:
return fmt.Errorf("%s: missing data payload %w", op, ErrInvalidParameter)
}
jf, err := c.create(ctx, s, storage.WithCreateFile(),
storage.WithFileAccessMode(storage.WriteOnly),
storage.WithCloseSyncMode(storage.Synchronous))
if err != nil {
return fmt.Errorf("%s: %w", op, err)
}
cf, err := checksum.NewFile(ctx, jf, c.checksums)
if err != nil {
return fmt.Errorf("%s: %w", op, err)
}
_, err = cf.Write(data)
if err != nil {
return fmt.Errorf("%s: %w", op, err)
}
err = cf.Close()
if err != nil {
return fmt.Errorf("%s: %w", op, err)
}
return nil
}
// writeMetaString writes a string to the containers meta file.
func (c *container) writeMetaString(_ context.Context, s string) (int, error) {
return c.meta.WriteString(s)
}
// writeMetaLine writes a new line terminated line to the container's meta file.
func (c *container) writeMetaLine(_ context.Context, s string) (int, error) {
return c.meta.WriteString(s + "\n")
}
// WriteMeta writes a new line terminated key : value pair to the container's meta file
func (c *container) WriteMeta(_ context.Context, k, v string) (int, error) {
return c.meta.WriteString(fmt.Sprintf("%s: %s\n", k, v))
}
// writeSummaryString writes a string to the container's summary file.
func (c *container) writeSummaryString(_ context.Context, s string) (int, error) {
return c.sum.WriteString(s)
}
// WriteSummary writes a new line terminated key : value pair to the container's summary file
func (c *container) WriteSummary(_ context.Context, k, v string) (int, error) {
return c.sum.WriteString(fmt.Sprintf("%s: %s\n", k, v))
}
// WriteSummaryLine writes a new line terminated string to the container's summary file.
func (c *container) WriteSummaryLine(_ context.Context, s string) (int, error) {
return c.sum.WriteString(s + "\n")
}
// WriteBinaryChecksum writes a checksum for a binary file to the checksum file.
func (c *container) WriteBinaryChecksum(_ context.Context, sum []byte, fname string) (int, error) {
return c.checksums.WriteString(fmt.Sprintf("%x *%s\n", sum, fname))
}
// close closes a container, closing the underlying files in a container.
func (c *container) close(_ context.Context) error {
const op = "bsr.(container).close"
var closeError error
if err := c.meta.Close(); err != nil {
closeError = errors.Join(closeError, fmt.Errorf("%s: %w", op, err))
}
if err := c.sum.Close(); err != nil {
closeError = errors.Join(closeError, fmt.Errorf("%s: %w", op, err))
}
if err := c.checksums.Close(); err != nil {
closeError = errors.Join(closeError, fmt.Errorf("%s: %w", op, err))
}
if err := c.sigs.Close(); err != nil {
closeError = errors.Join(closeError, fmt.Errorf("%s: %w", op, err))
}
if err := c.journal.Close(); err != nil {
closeError = errors.Join(closeError, fmt.Errorf("%s: %w", op, err))
}
if err := c.container.Close(); err != nil {
closeError = errors.Join(closeError, fmt.Errorf("%s: %w", op, err))
}
return closeError
}