Compare commits
2 Commits
56f796e3fb
...
9d5c8ea4db
Author | SHA1 | Date | |
---|---|---|---|
9d5c8ea4db | |||
86abdb6ae1 |
@ -4,7 +4,6 @@ package admin
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"isle/garage"
|
|
||||||
"isle/nebula"
|
"isle/nebula"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -23,10 +22,6 @@ type Admin struct {
|
|||||||
Nebula struct {
|
Nebula struct {
|
||||||
CACredentials nebula.CACredentials
|
CACredentials nebula.CACredentials
|
||||||
}
|
}
|
||||||
|
|
||||||
Garage struct {
|
|
||||||
GlobalBucketS3APICredentials garage.S3APICredentials
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromReader reads an admin.json from the given io.Reader.
|
// FromReader reads an admin.json from the given io.Reader.
|
||||||
|
@ -6,12 +6,9 @@ import (
|
|||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"isle/admin"
|
"isle/admin"
|
||||||
"isle/garage"
|
|
||||||
"isle/nebula"
|
"isle/nebula"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
)
|
)
|
||||||
@ -28,20 +25,11 @@ func AppDirPath(appDirPath string) string {
|
|||||||
return filepath.Join(appDirPath, "share/bootstrap.json")
|
return filepath.Join(appDirPath, "share/bootstrap.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Garage contains parameters needed to connect to and use the garage cluster.
|
// Bootstrap contains all information which is needed by a host daemon to join a
|
||||||
type Garage struct {
|
// network on boot.
|
||||||
AdminToken string
|
|
||||||
|
|
||||||
// TODO this should be part of admin.CreationParams
|
|
||||||
GlobalBucketS3APICredentials garage.S3APICredentials
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bootstrap is used for accessing all information contained within a
|
|
||||||
// bootstrap.json file.
|
|
||||||
type Bootstrap struct {
|
type Bootstrap struct {
|
||||||
AdminCreationParams admin.CreationParams
|
AdminCreationParams admin.CreationParams
|
||||||
CAPublicCredentials nebula.CAPublicCredentials
|
CAPublicCredentials nebula.CAPublicCredentials
|
||||||
Garage Garage
|
|
||||||
|
|
||||||
PrivateCredentials nebula.HostPrivateCredentials
|
PrivateCredentials nebula.HostPrivateCredentials
|
||||||
HostAssigned `json:"-"`
|
HostAssigned `json:"-"`
|
||||||
@ -55,7 +43,6 @@ type Bootstrap struct {
|
|||||||
func New(
|
func New(
|
||||||
caCreds nebula.CACredentials,
|
caCreds nebula.CACredentials,
|
||||||
adminCreationParams admin.CreationParams,
|
adminCreationParams admin.CreationParams,
|
||||||
garage Garage,
|
|
||||||
name nebula.HostName,
|
name nebula.HostName,
|
||||||
ip netip.Addr,
|
ip netip.Addr,
|
||||||
) (
|
) (
|
||||||
@ -81,7 +68,6 @@ func New(
|
|||||||
return Bootstrap{
|
return Bootstrap{
|
||||||
AdminCreationParams: adminCreationParams,
|
AdminCreationParams: adminCreationParams,
|
||||||
CAPublicCredentials: caCreds.Public,
|
CAPublicCredentials: caCreds.Public,
|
||||||
Garage: garage,
|
|
||||||
PrivateCredentials: hostPrivCreds,
|
PrivateCredentials: hostPrivCreds,
|
||||||
HostAssigned: assigned,
|
HostAssigned: assigned,
|
||||||
SignedHostAssigned: signedAssigned,
|
SignedHostAssigned: signedAssigned,
|
||||||
@ -89,23 +75,9 @@ func New(
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromFile reads a bootstrap from a file at the given path. The HostAssigned
|
// UnmarshalJSON implements the json.Unmarshaler interface. It will
|
||||||
// field will automatically be unwrapped.
|
// automatically populate the HostAssigned field by unwrapping the
|
||||||
func FromFile(path string) (Bootstrap, error) {
|
// SignedHostAssigned field.
|
||||||
f, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return Bootstrap{}, fmt.Errorf("opening file: %w", err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
var b Bootstrap
|
|
||||||
if err := json.NewDecoder(f).Decode(&b); err != nil {
|
|
||||||
return Bootstrap{}, fmt.Errorf("decoding json: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bootstrap) UnmarshalJSON(data []byte) error {
|
func (b *Bootstrap) UnmarshalJSON(data []byte) error {
|
||||||
type inner Bootstrap
|
type inner Bootstrap
|
||||||
|
|
||||||
@ -124,11 +96,6 @@ func (b *Bootstrap) UnmarshalJSON(data []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteTo writes the Bootstrap as a new bootstrap to the given io.Writer.
|
|
||||||
func (b Bootstrap) WriteTo(into io.Writer) error {
|
|
||||||
return json.NewEncoder(into).Encode(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ThisHost is a shortcut for b.Hosts[b.HostName], but will panic if the
|
// ThisHost is a shortcut for b.Hosts[b.HostName], but will panic if the
|
||||||
// HostName isn't found in the Hosts map.
|
// HostName isn't found in the Hosts map.
|
||||||
func (b Bootstrap) ThisHost() Host {
|
func (b Bootstrap) ThisHost() Host {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"isle/bootstrap"
|
"isle/bootstrap"
|
||||||
@ -67,7 +68,7 @@ var subCmdHostsCreate = subCmd{
|
|||||||
return fmt.Errorf("calling CreateHost: %w", err)
|
return fmt.Errorf("calling CreateHost: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.HostBootstrap.WriteTo(os.Stdout)
|
return json.NewEncoder(os.Stdout).Encode(res.JoiningBootstrap)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,8 +4,8 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"isle/admin"
|
"isle/admin"
|
||||||
"isle/bootstrap"
|
|
||||||
"isle/daemon"
|
"isle/daemon"
|
||||||
|
"isle/jsonutil"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -88,8 +88,8 @@ var subCmdNetworkJoin = subCmd{
|
|||||||
return errors.New("--bootstrap-path is required")
|
return errors.New("--bootstrap-path is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
newBootstrap, err := bootstrap.FromFile(*bootstrapPath)
|
var newBootstrap daemon.JoiningBootstrap
|
||||||
if err != nil {
|
if err := jsonutil.LoadFile(&newBootstrap, *bootstrapPath); err != nil {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"loading bootstrap from %q: %w", *bootstrapPath, err,
|
"loading bootstrap from %q: %w", *bootstrapPath, err,
|
||||||
)
|
)
|
||||||
|
@ -1,14 +1,24 @@
|
|||||||
package daemon
|
package daemon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"isle/bootstrap"
|
"isle/bootstrap"
|
||||||
"isle/garage/garagesrv"
|
"isle/garage/garagesrv"
|
||||||
|
"isle/jsonutil"
|
||||||
|
"isle/secrets"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// JoiningBootstrap wraps a normal Bootstrap to include extra data which a host
|
||||||
|
// might need while joining a network.
|
||||||
|
type JoiningBootstrap struct {
|
||||||
|
Bootstrap bootstrap.Bootstrap
|
||||||
|
Secrets map[secrets.ID]json.RawMessage
|
||||||
|
}
|
||||||
|
|
||||||
func writeBootstrapToStateDir(
|
func writeBootstrapToStateDir(
|
||||||
stateDirPath string, hostBootstrap bootstrap.Bootstrap,
|
stateDirPath string, hostBootstrap bootstrap.Bootstrap,
|
||||||
) error {
|
) error {
|
||||||
@ -21,14 +31,11 @@ func writeBootstrapToStateDir(
|
|||||||
return fmt.Errorf("creating directory %q: %w", dirPath, err)
|
return fmt.Errorf("creating directory %q: %w", dirPath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.Create(path)
|
if err := jsonutil.WriteFile(hostBootstrap, path, 0700); err != nil {
|
||||||
if err != nil {
|
return fmt.Errorf("writing bootstrap to %q: %w", path, err)
|
||||||
return fmt.Errorf("creating file %q: %w", path, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defer f.Close()
|
return nil
|
||||||
|
|
||||||
return hostBootstrap.WriteTo(f)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func coalesceDaemonConfigAndBootstrap(
|
func coalesceDaemonConfigAndBootstrap(
|
||||||
|
@ -19,11 +19,12 @@ func garageAdminClientLogger(logger *mlog.Logger) *mlog.Logger {
|
|||||||
return logger.WithNamespace("garageAdminClient")
|
return logger.WithNamespace("garageAdminClient")
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGarageAdminClient will return an AdminClient for a local garage instance,
|
// newGarageAdminClient will return an AdminClient for a local garage instance,
|
||||||
// or it will _panic_ if there is no local instance configured.
|
// or it will _panic_ if there is no local instance configured.
|
||||||
func NewGarageAdminClient(
|
func newGarageAdminClient(
|
||||||
logger *mlog.Logger,
|
logger *mlog.Logger,
|
||||||
daemonConfig Config,
|
daemonConfig Config,
|
||||||
|
adminToken string,
|
||||||
hostBootstrap bootstrap.Bootstrap,
|
hostBootstrap bootstrap.Bootstrap,
|
||||||
) *garage.AdminClient {
|
) *garage.AdminClient {
|
||||||
|
|
||||||
@ -35,7 +36,7 @@ func NewGarageAdminClient(
|
|||||||
thisHost.IP().String(),
|
thisHost.IP().String(),
|
||||||
strconv.Itoa(daemonConfig.Storage.Allocations[0].AdminPort),
|
strconv.Itoa(daemonConfig.Storage.Allocations[0].AdminPort),
|
||||||
),
|
),
|
||||||
hostBootstrap.Garage.AdminToken,
|
adminToken,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,6 +44,7 @@ func waitForGarage(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
logger *mlog.Logger,
|
logger *mlog.Logger,
|
||||||
daemonConfig Config,
|
daemonConfig Config,
|
||||||
|
adminToken string,
|
||||||
hostBootstrap bootstrap.Bootstrap,
|
hostBootstrap bootstrap.Bootstrap,
|
||||||
) error {
|
) error {
|
||||||
|
|
||||||
@ -64,9 +66,7 @@ func waitForGarage(
|
|||||||
)
|
)
|
||||||
|
|
||||||
adminClient := garage.NewAdminClient(
|
adminClient := garage.NewAdminClient(
|
||||||
adminClientLogger,
|
adminClientLogger, adminAddr, adminToken,
|
||||||
adminAddr,
|
|
||||||
hostBootstrap.Garage.AdminToken,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
ctx := mctx.Annotate(ctx, "garageAdminAddr", adminAddr)
|
ctx := mctx.Annotate(ctx, "garageAdminAddr", adminAddr)
|
||||||
@ -101,7 +101,7 @@ func bootstrapGarageHostForAlloc(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func garageWriteChildConfig(
|
func garageWriteChildConfig(
|
||||||
rpcSecret, runtimeDirPath string,
|
rpcSecret, runtimeDirPath, adminToken string,
|
||||||
hostBootstrap bootstrap.Bootstrap,
|
hostBootstrap bootstrap.Bootstrap,
|
||||||
alloc ConfigStorageAllocation,
|
alloc ConfigStorageAllocation,
|
||||||
) (
|
) (
|
||||||
@ -130,7 +130,7 @@ func garageWriteChildConfig(
|
|||||||
DataPath: alloc.DataPath,
|
DataPath: alloc.DataPath,
|
||||||
|
|
||||||
RPCSecret: rpcSecret,
|
RPCSecret: rpcSecret,
|
||||||
AdminToken: hostBootstrap.Garage.AdminToken,
|
AdminToken: adminToken,
|
||||||
|
|
||||||
LocalPeer: peer,
|
LocalPeer: peer,
|
||||||
BootstrapPeers: hostBootstrap.GaragePeers(),
|
BootstrapPeers: hostBootstrap.GaragePeers(),
|
||||||
@ -148,6 +148,7 @@ func garagePmuxProcConfigs(
|
|||||||
logger *mlog.Logger,
|
logger *mlog.Logger,
|
||||||
rpcSecret, runtimeDirPath, binDirPath string,
|
rpcSecret, runtimeDirPath, binDirPath string,
|
||||||
daemonConfig Config,
|
daemonConfig Config,
|
||||||
|
adminToken string,
|
||||||
hostBootstrap bootstrap.Bootstrap,
|
hostBootstrap bootstrap.Bootstrap,
|
||||||
) (
|
) (
|
||||||
[]pmuxlib.ProcessConfig, error,
|
[]pmuxlib.ProcessConfig, error,
|
||||||
@ -165,7 +166,7 @@ func garagePmuxProcConfigs(
|
|||||||
for _, alloc := range allocs {
|
for _, alloc := range allocs {
|
||||||
|
|
||||||
childConfigPath, err := garageWriteChildConfig(
|
childConfigPath, err := garageWriteChildConfig(
|
||||||
rpcSecret, runtimeDirPath, hostBootstrap, alloc,
|
rpcSecret, runtimeDirPath, adminToken, hostBootstrap, alloc,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("writing child config file for alloc %+v: %w", alloc, err)
|
return nil, fmt.Errorf("writing child config file for alloc %+v: %w", alloc, err)
|
||||||
@ -188,11 +189,14 @@ func garageApplyLayout(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
logger *mlog.Logger,
|
logger *mlog.Logger,
|
||||||
daemonConfig Config,
|
daemonConfig Config,
|
||||||
|
adminToken string,
|
||||||
hostBootstrap bootstrap.Bootstrap,
|
hostBootstrap bootstrap.Bootstrap,
|
||||||
) error {
|
) error {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
adminClient = NewGarageAdminClient(logger, daemonConfig, hostBootstrap)
|
adminClient = newGarageAdminClient(
|
||||||
|
logger, daemonConfig, adminToken, hostBootstrap,
|
||||||
|
)
|
||||||
thisHost = hostBootstrap.ThisHost()
|
thisHost = hostBootstrap.ThisHost()
|
||||||
hostName = thisHost.Name
|
hostName = thisHost.Name
|
||||||
allocs = daemonConfig.Storage.Allocations
|
allocs = daemonConfig.Storage.Allocations
|
||||||
|
@ -12,6 +12,7 @@ func (c *Children) newPmuxConfig(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
garageRPCSecret, binDirPath string,
|
garageRPCSecret, binDirPath string,
|
||||||
daemonConfig Config,
|
daemonConfig Config,
|
||||||
|
garageAdminToken string,
|
||||||
hostBootstrap bootstrap.Bootstrap,
|
hostBootstrap bootstrap.Bootstrap,
|
||||||
) (
|
) (
|
||||||
pmuxlib.Config, error,
|
pmuxlib.Config, error,
|
||||||
@ -45,6 +46,7 @@ func (c *Children) newPmuxConfig(
|
|||||||
c.opts.EnvVars.RuntimeDirPath,
|
c.opts.EnvVars.RuntimeDirPath,
|
||||||
binDirPath,
|
binDirPath,
|
||||||
daemonConfig,
|
daemonConfig,
|
||||||
|
garageAdminToken,
|
||||||
hostBootstrap,
|
hostBootstrap,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -67,6 +69,7 @@ func (c *Children) newPmuxConfig(
|
|||||||
func (c *Children) postPmuxInit(
|
func (c *Children) postPmuxInit(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
daemonConfig Config,
|
daemonConfig Config,
|
||||||
|
garageAdminToken string,
|
||||||
hostBootstrap bootstrap.Bootstrap,
|
hostBootstrap bootstrap.Bootstrap,
|
||||||
) error {
|
) error {
|
||||||
c.logger.Info(ctx, "Waiting for nebula VPN to come online")
|
c.logger.Info(ctx, "Waiting for nebula VPN to come online")
|
||||||
@ -75,7 +78,9 @@ func (c *Children) postPmuxInit(
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.logger.Info(ctx, "Waiting for garage instances to come online")
|
c.logger.Info(ctx, "Waiting for garage instances to come online")
|
||||||
err := waitForGarage(ctx, c.logger, daemonConfig, hostBootstrap)
|
err := waitForGarage(
|
||||||
|
ctx, c.logger, daemonConfig, garageAdminToken, hostBootstrap,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("waiting for garage to start: %w", err)
|
return fmt.Errorf("waiting for garage to start: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"isle/bootstrap"
|
"isle/bootstrap"
|
||||||
"isle/garage"
|
|
||||||
"isle/secrets"
|
"isle/secrets"
|
||||||
|
|
||||||
"code.betamike.com/micropelago/pmux/pmuxlib"
|
"code.betamike.com/micropelago/pmux/pmuxlib"
|
||||||
@ -33,6 +32,7 @@ func NewChildren(
|
|||||||
binDirPath string,
|
binDirPath string,
|
||||||
secretsStore secrets.Store,
|
secretsStore secrets.Store,
|
||||||
daemonConfig Config,
|
daemonConfig Config,
|
||||||
|
garageAdminToken string,
|
||||||
hostBootstrap bootstrap.Bootstrap,
|
hostBootstrap bootstrap.Bootstrap,
|
||||||
opts *Opts,
|
opts *Opts,
|
||||||
) (
|
) (
|
||||||
@ -41,7 +41,7 @@ func NewChildren(
|
|||||||
opts = opts.withDefaults()
|
opts = opts.withDefaults()
|
||||||
|
|
||||||
logger.Info(ctx, "Loading secrets")
|
logger.Info(ctx, "Loading secrets")
|
||||||
garageRPCSecret, err := garage.GetRPCSecret(ctx, secretsStore)
|
garageRPCSecret, err := getGarageRPCSecret(ctx, secretsStore)
|
||||||
if err != nil && !errors.Is(err, secrets.ErrNotFound) {
|
if err != nil && !errors.Is(err, secrets.ErrNotFound) {
|
||||||
return nil, fmt.Errorf("loading garage RPC secret: %w", err)
|
return nil, fmt.Errorf("loading garage RPC secret: %w", err)
|
||||||
}
|
}
|
||||||
@ -56,7 +56,12 @@ func NewChildren(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pmuxConfig, err := c.newPmuxConfig(
|
pmuxConfig, err := c.newPmuxConfig(
|
||||||
ctx, garageRPCSecret, binDirPath, daemonConfig, hostBootstrap,
|
ctx,
|
||||||
|
garageRPCSecret,
|
||||||
|
binDirPath,
|
||||||
|
daemonConfig,
|
||||||
|
garageAdminToken,
|
||||||
|
hostBootstrap,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("generating pmux config: %w", err)
|
return nil, fmt.Errorf("generating pmux config: %w", err)
|
||||||
@ -68,7 +73,9 @@ func NewChildren(
|
|||||||
c.logger.Debug(pmuxCtx, "pmux stopped")
|
c.logger.Debug(pmuxCtx, "pmux stopped")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
initErr := c.postPmuxInit(ctx, daemonConfig, hostBootstrap)
|
initErr := c.postPmuxInit(
|
||||||
|
ctx, daemonConfig, garageAdminToken, hostBootstrap,
|
||||||
|
)
|
||||||
if initErr != nil {
|
if initErr != nil {
|
||||||
logger.Warn(ctx, "failed to initialize Children, shutting down child processes", err)
|
logger.Warn(ctx, "failed to initialize Children, shutting down child processes", err)
|
||||||
if err := c.Shutdown(); err != nil {
|
if err := c.Shutdown(); err != nil {
|
||||||
|
@ -11,7 +11,7 @@ import (
|
|||||||
"io/fs"
|
"io/fs"
|
||||||
"isle/admin"
|
"isle/admin"
|
||||||
"isle/bootstrap"
|
"isle/bootstrap"
|
||||||
"isle/garage"
|
"isle/jsonutil"
|
||||||
"isle/nebula"
|
"isle/nebula"
|
||||||
"isle/secrets"
|
"isle/secrets"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
@ -50,7 +50,7 @@ type Daemon interface {
|
|||||||
//
|
//
|
||||||
// Errors:
|
// Errors:
|
||||||
// - ErrAlreadyJoined
|
// - ErrAlreadyJoined
|
||||||
JoinNetwork(context.Context, bootstrap.Bootstrap) error
|
JoinNetwork(context.Context, JoiningBootstrap) error
|
||||||
|
|
||||||
// GetBootstraps returns the currently active Bootstrap.
|
// GetBootstraps returns the currently active Bootstrap.
|
||||||
GetBootstrap(context.Context) (bootstrap.Bootstrap, error)
|
GetBootstrap(context.Context) (bootstrap.Bootstrap, error)
|
||||||
@ -70,7 +70,7 @@ type Daemon interface {
|
|||||||
hostName nebula.HostName,
|
hostName nebula.HostName,
|
||||||
ip netip.Addr, // TODO automatically choose IP address
|
ip netip.Addr, // TODO automatically choose IP address
|
||||||
) (
|
) (
|
||||||
bootstrap.Bootstrap, error,
|
JoiningBootstrap, error,
|
||||||
)
|
)
|
||||||
|
|
||||||
// CreateNebulaCertificate creates and signs a new nebula certficate for an
|
// CreateNebulaCertificate creates and signs a new nebula certficate for an
|
||||||
@ -141,6 +141,7 @@ type daemon struct {
|
|||||||
opts *Opts
|
opts *Opts
|
||||||
|
|
||||||
secretsStore secrets.Store
|
secretsStore secrets.Store
|
||||||
|
garageAdminToken string
|
||||||
|
|
||||||
l sync.RWMutex
|
l sync.RWMutex
|
||||||
state int
|
state int
|
||||||
@ -180,6 +181,7 @@ func NewDaemon(
|
|||||||
daemonConfig: daemonConfig,
|
daemonConfig: daemonConfig,
|
||||||
envBinDirPath: envBinDirPath,
|
envBinDirPath: envBinDirPath,
|
||||||
opts: opts.withDefaults(),
|
opts: opts.withDefaults(),
|
||||||
|
garageAdminToken: randStr(32),
|
||||||
shutdownCh: make(chan struct{}),
|
shutdownCh: make(chan struct{}),
|
||||||
}
|
}
|
||||||
bootstrapFilePath = bootstrap.StateDirPath(d.opts.EnvVars.StateDirPath)
|
bootstrapFilePath = bootstrap.StateDirPath(d.opts.EnvVars.StateDirPath)
|
||||||
@ -200,7 +202,8 @@ func NewDaemon(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
currBootstrap, err := bootstrap.FromFile(bootstrapFilePath)
|
var currBootstrap bootstrap.Bootstrap
|
||||||
|
err = jsonutil.LoadFile(&currBootstrap, bootstrapFilePath)
|
||||||
if errors.Is(err, fs.ErrNotExist) {
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
// daemon has never had a network created or joined
|
// daemon has never had a network created or joined
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
@ -368,7 +371,11 @@ func (d *daemon) postInit(ctx context.Context) bool {
|
|||||||
"Applying garage layout",
|
"Applying garage layout",
|
||||||
func(ctx context.Context) error {
|
func(ctx context.Context) error {
|
||||||
return garageApplyLayout(
|
return garageApplyLayout(
|
||||||
ctx, d.logger, d.daemonConfig, d.currBootstrap,
|
ctx,
|
||||||
|
d.logger,
|
||||||
|
d.daemonConfig,
|
||||||
|
d.garageAdminToken,
|
||||||
|
d.currBootstrap,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
@ -381,28 +388,29 @@ func (d *daemon) postInit(ctx context.Context) bool {
|
|||||||
//
|
//
|
||||||
// TODO this is pretty hacky, but there doesn't seem to be a better way to
|
// TODO this is pretty hacky, but there doesn't seem to be a better way to
|
||||||
// manage it at the moment.
|
// manage it at the moment.
|
||||||
if d.currBootstrap.Garage.GlobalBucketS3APICredentials == (garage.S3APICredentials{}) {
|
_, err := getGarageS3APIGlobalBucketCredentials(ctx, d.secretsStore)
|
||||||
currBootstrap := d.currBootstrap
|
if errors.Is(err, secrets.ErrNotFound) {
|
||||||
if !until(
|
if !until(
|
||||||
ctx,
|
ctx,
|
||||||
d.logger,
|
d.logger,
|
||||||
"Initializing garage shared global bucket",
|
"Initializing garage shared global bucket",
|
||||||
func(ctx context.Context) error {
|
func(ctx context.Context) error {
|
||||||
garageGlobalBucketCreds, err := garageInitializeGlobalBucket(
|
garageGlobalBucketCreds, err := garageInitializeGlobalBucket(
|
||||||
ctx, d.logger, d.daemonConfig, d.currBootstrap,
|
ctx,
|
||||||
|
d.logger,
|
||||||
|
d.daemonConfig,
|
||||||
|
d.garageAdminToken,
|
||||||
|
d.currBootstrap,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("initializing global bucket: %w", err)
|
return fmt.Errorf("initializing global bucket: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
currBootstrap.Garage.GlobalBucketS3APICredentials = garageGlobalBucketCreds
|
err = setGarageS3APIGlobalBucketCredentials(
|
||||||
|
ctx, d.secretsStore, garageGlobalBucketCreds,
|
||||||
d.logger.Info(ctx, "Writing bootstrap to state directory")
|
|
||||||
err = writeBootstrapToStateDir(
|
|
||||||
d.opts.EnvVars.StateDirPath, currBootstrap,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("writing bootstrap to state dir: %w", err)
|
return fmt.Errorf("storing global bucket creds: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -410,10 +418,6 @@ func (d *daemon) postInit(ctx context.Context) bool {
|
|||||||
) {
|
) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
d.l.Lock()
|
|
||||||
d.currBootstrap = currBootstrap
|
|
||||||
d.l.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !until(
|
if !until(
|
||||||
@ -459,6 +463,7 @@ func (d *daemon) restartLoop(ctx context.Context, readyCh chan<- struct{}) {
|
|||||||
d.envBinDirPath,
|
d.envBinDirPath,
|
||||||
d.secretsStore,
|
d.secretsStore,
|
||||||
d.daemonConfig,
|
d.daemonConfig,
|
||||||
|
d.garageAdminToken,
|
||||||
d.currBootstrap,
|
d.currBootstrap,
|
||||||
d.opts,
|
d.opts,
|
||||||
)
|
)
|
||||||
@ -531,14 +536,10 @@ func (d *daemon) CreateNetwork(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
garageBootstrap = bootstrap.Garage{
|
garageRPCSecret = randStr(32)
|
||||||
AdminToken: randStr(32),
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
garageRPCSecret := randStr(32)
|
err = setGarageRPCSecret(ctx, d.secretsStore, garageRPCSecret)
|
||||||
|
|
||||||
err = garage.SetRPCSecret(ctx, d.secretsStore, garageRPCSecret)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return admin.Admin{}, fmt.Errorf("storing garage RPC secret: %w", err)
|
return admin.Admin{}, fmt.Errorf("storing garage RPC secret: %w", err)
|
||||||
}
|
}
|
||||||
@ -546,7 +547,6 @@ func (d *daemon) CreateNetwork(
|
|||||||
hostBootstrap, err := bootstrap.New(
|
hostBootstrap, err := bootstrap.New(
|
||||||
nebulaCACreds,
|
nebulaCACreds,
|
||||||
adm.CreationParams,
|
adm.CreationParams,
|
||||||
garageBootstrap,
|
|
||||||
hostName,
|
hostName,
|
||||||
ipNet.FirstAddr(),
|
ipNet.FirstAddr(),
|
||||||
)
|
)
|
||||||
@ -582,22 +582,13 @@ func (d *daemon) CreateNetwork(
|
|||||||
return adm, ctx.Err()
|
return adm, ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// As part of postInit, which is called prior to ready(), the restartLoop
|
|
||||||
// will check if the global bucket creds have been created yet or not, and
|
|
||||||
// create them if so. So once ready() is called we can get the created creds
|
|
||||||
// from the currBootstrap
|
|
||||||
d.l.RLock()
|
|
||||||
garageGlobalBucketCreds := d.currBootstrap.Garage.GlobalBucketS3APICredentials
|
|
||||||
d.l.RUnlock()
|
|
||||||
|
|
||||||
adm.Nebula.CACredentials = nebulaCACreds
|
adm.Nebula.CACredentials = nebulaCACreds
|
||||||
adm.Garage.GlobalBucketS3APICredentials = garageGlobalBucketCreds
|
|
||||||
|
|
||||||
return adm, nil
|
return adm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *daemon) JoinNetwork(
|
func (d *daemon) JoinNetwork(
|
||||||
ctx context.Context, newBootstrap bootstrap.Bootstrap,
|
ctx context.Context, newBootstrap JoiningBootstrap,
|
||||||
) error {
|
) error {
|
||||||
d.l.Lock()
|
d.l.Lock()
|
||||||
|
|
||||||
@ -608,7 +599,13 @@ func (d *daemon) JoinNetwork(
|
|||||||
|
|
||||||
readyCh := make(chan struct{}, 1)
|
readyCh := make(chan struct{}, 1)
|
||||||
|
|
||||||
err := d.initialize(newBootstrap, readyCh)
|
err := secrets.Import(ctx, d.secretsStore, newBootstrap.Secrets)
|
||||||
|
if err != nil {
|
||||||
|
d.l.Unlock()
|
||||||
|
return fmt.Errorf("importing secrets: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = d.initialize(newBootstrap.Bootstrap, readyCh)
|
||||||
d.l.Unlock()
|
d.l.Unlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("initializing daemon: %w", err)
|
return fmt.Errorf("initializing daemon: %w", err)
|
||||||
@ -681,39 +678,45 @@ func (d *daemon) CreateHost(
|
|||||||
hostName nebula.HostName,
|
hostName nebula.HostName,
|
||||||
ip netip.Addr,
|
ip netip.Addr,
|
||||||
) (
|
) (
|
||||||
bootstrap.Bootstrap, error,
|
JoiningBootstrap, error,
|
||||||
) {
|
) {
|
||||||
return withCurrBootstrap(d, func(
|
return withCurrBootstrap(d, func(
|
||||||
currBootstrap bootstrap.Bootstrap,
|
currBootstrap bootstrap.Bootstrap,
|
||||||
) (
|
) (
|
||||||
bootstrap.Bootstrap, error,
|
JoiningBootstrap, error,
|
||||||
) {
|
) {
|
||||||
garageGlobalBucketS3APICreds := currBootstrap.Garage.GlobalBucketS3APICredentials
|
var (
|
||||||
|
joiningBootstrap JoiningBootstrap
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
garageBootstrap := bootstrap.Garage{
|
joiningBootstrap.Bootstrap, err = bootstrap.New(
|
||||||
AdminToken: randStr(32),
|
|
||||||
GlobalBucketS3APICredentials: garageGlobalBucketS3APICreds,
|
|
||||||
}
|
|
||||||
|
|
||||||
newHostBootstrap, err := bootstrap.New(
|
|
||||||
makeCACreds(currBootstrap, caSigningPrivateKey),
|
makeCACreds(currBootstrap, caSigningPrivateKey),
|
||||||
currBootstrap.AdminCreationParams,
|
currBootstrap.AdminCreationParams,
|
||||||
garageBootstrap,
|
|
||||||
hostName,
|
hostName,
|
||||||
ip,
|
ip,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return bootstrap.Bootstrap{}, fmt.Errorf(
|
return JoiningBootstrap{}, fmt.Errorf(
|
||||||
"initializing bootstrap data: %w", err,
|
"initializing bootstrap data: %w", err,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
newHostBootstrap.Hosts = currBootstrap.Hosts
|
joiningBootstrap.Bootstrap.Hosts = currBootstrap.Hosts
|
||||||
|
|
||||||
|
if joiningBootstrap.Secrets, err = secrets.Export(
|
||||||
|
ctx, d.secretsStore, []secrets.ID{
|
||||||
|
garageRPCSecretSecretID,
|
||||||
|
garageS3APIGlobalBucketCredentialsSecretID,
|
||||||
|
},
|
||||||
|
); err != nil {
|
||||||
|
return JoiningBootstrap{}, fmt.Errorf("exporting secrets: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO persist new bootstrap to garage. Requires making the daemon
|
// TODO persist new bootstrap to garage. Requires making the daemon
|
||||||
// config change watching logic smarter, so only dnsmasq gets restarted.
|
// config change watching logic smarter, so only dnsmasq gets restarted.
|
||||||
|
|
||||||
return newHostBootstrap, nil
|
return joiningBootstrap, nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,14 +24,19 @@ func (d *daemon) getGarageClientParams(
|
|||||||
) (
|
) (
|
||||||
GarageClientParams, error,
|
GarageClientParams, error,
|
||||||
) {
|
) {
|
||||||
rpcSecret, err := garage.GetRPCSecret(ctx, d.secretsStore)
|
creds, err := getGarageS3APIGlobalBucketCredentials(ctx, d.secretsStore)
|
||||||
|
if err != nil {
|
||||||
|
return GarageClientParams{}, fmt.Errorf("getting garage global bucket creds: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rpcSecret, err := getGarageRPCSecret(ctx, d.secretsStore)
|
||||||
if err != nil && !errors.Is(err, secrets.ErrNotFound) {
|
if err != nil && !errors.Is(err, secrets.ErrNotFound) {
|
||||||
return GarageClientParams{}, fmt.Errorf("getting garage rpc secret: %w", err)
|
return GarageClientParams{}, fmt.Errorf("getting garage rpc secret: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return GarageClientParams{
|
return GarageClientParams{
|
||||||
Peer: currBootstrap.ChooseGaragePeer(),
|
Peer: currBootstrap.ChooseGaragePeer(),
|
||||||
GlobalBucketS3APICredentials: currBootstrap.Garage.GlobalBucketS3APICredentials,
|
GlobalBucketS3APICredentials: creds,
|
||||||
RPCSecret: rpcSecret,
|
RPCSecret: rpcSecret,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -24,12 +24,13 @@ func garageInitializeGlobalBucket(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
logger *mlog.Logger,
|
logger *mlog.Logger,
|
||||||
daemonConfig Config,
|
daemonConfig Config,
|
||||||
|
adminToken string,
|
||||||
hostBootstrap bootstrap.Bootstrap,
|
hostBootstrap bootstrap.Bootstrap,
|
||||||
) (
|
) (
|
||||||
garage.S3APICredentials, error,
|
garage.S3APICredentials, error,
|
||||||
) {
|
) {
|
||||||
adminClient := NewGarageAdminClient(
|
adminClient := newGarageAdminClient(
|
||||||
logger, daemonConfig, hostBootstrap,
|
logger, daemonConfig, adminToken, hostBootstrap,
|
||||||
)
|
)
|
||||||
|
|
||||||
creds, err := adminClient.CreateS3APICredentials(
|
creds, err := adminClient.CreateS3APICredentials(
|
||||||
|
@ -56,7 +56,7 @@ func (r *RPC) CreateNetwork(
|
|||||||
|
|
||||||
// JoinNetwork passes through to the Daemon method of the same name.
|
// JoinNetwork passes through to the Daemon method of the same name.
|
||||||
func (r *RPC) JoinNetwork(
|
func (r *RPC) JoinNetwork(
|
||||||
ctx context.Context, req bootstrap.Bootstrap,
|
ctx context.Context, req JoiningBootstrap,
|
||||||
) (
|
) (
|
||||||
struct{}, error,
|
struct{}, error,
|
||||||
) {
|
) {
|
||||||
@ -137,7 +137,7 @@ type CreateHostRequest struct {
|
|||||||
|
|
||||||
// CreateHostResult wraps the results from the CreateHost RPC method.
|
// CreateHostResult wraps the results from the CreateHost RPC method.
|
||||||
type CreateHostResult struct {
|
type CreateHostResult struct {
|
||||||
HostBootstrap bootstrap.Bootstrap
|
JoiningBootstrap JoiningBootstrap
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateHost passes the call through to the Daemon method of the
|
// CreateHost passes the call through to the Daemon method of the
|
||||||
@ -147,14 +147,14 @@ func (r *RPC) CreateHost(
|
|||||||
) (
|
) (
|
||||||
CreateHostResult, error,
|
CreateHostResult, error,
|
||||||
) {
|
) {
|
||||||
hostBootstrap, err := r.daemon.CreateHost(
|
joiningBootstrap, err := r.daemon.CreateHost(
|
||||||
ctx, req.CASigningPrivateKey, req.HostName, req.IP,
|
ctx, req.CASigningPrivateKey, req.HostName, req.IP,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CreateHostResult{}, err
|
return CreateHostResult{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return CreateHostResult{HostBootstrap: hostBootstrap}, nil
|
return CreateHostResult{JoiningBootstrap: joiningBootstrap}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateNebulaCertificateRequest contains the arguments to the
|
// CreateNebulaCertificateRequest contains the arguments to the
|
||||||
|
39
go/daemon/secrets.go
Normal file
39
go/daemon/secrets.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package daemon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"isle/garage"
|
||||||
|
"isle/secrets"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
secretsNSGarage = "garage"
|
||||||
|
)
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Garage-related secrets
|
||||||
|
|
||||||
|
func garageS3APIBucketCredentialsSecretID(credsName string) secrets.ID {
|
||||||
|
return secrets.NewID(
|
||||||
|
secretsNSGarage, fmt.Sprintf("s3-api-bucket-credentials-%s", credsName),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
garageRPCSecretSecretID = secrets.NewID(secretsNSGarage, "rpc-secret")
|
||||||
|
garageS3APIGlobalBucketCredentialsSecretID = garageS3APIBucketCredentialsSecretID(
|
||||||
|
garage.GlobalBucketS3APICredentialsName,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get/Set functions for garage-related secrets.
|
||||||
|
var (
|
||||||
|
getGarageRPCSecret, setGarageRPCSecret = secrets.GetSetFunctions[string](
|
||||||
|
garageRPCSecretSecretID,
|
||||||
|
)
|
||||||
|
|
||||||
|
getGarageS3APIGlobalBucketCredentials,
|
||||||
|
setGarageS3APIGlobalBucketCredentials = secrets.GetSetFunctions[garage.S3APICredentials](
|
||||||
|
garageS3APIGlobalBucketCredentialsSecretID,
|
||||||
|
)
|
||||||
|
)
|
@ -1,20 +0,0 @@
|
|||||||
package garage
|
|
||||||
|
|
||||||
import "isle/secrets"
|
|
||||||
|
|
||||||
var (
|
|
||||||
rpcSecretSecretID = secrets.NewID("garage", "rpc-secret")
|
|
||||||
globalBucketS3APICredentialsSecretID = secrets.NewID("garage", "global-bucket-s3-api-credentials")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Get/Set functions for garage-related secrets.
|
|
||||||
var (
|
|
||||||
GetRPCSecret, SetRPCSecret = secrets.GetSetFunctions[string](
|
|
||||||
rpcSecretSecretID,
|
|
||||||
)
|
|
||||||
|
|
||||||
GetGlobalBucketS3APICredentials,
|
|
||||||
SetGlobalBucketS3APICredentials = secrets.GetSetFunctions[S3APICredentials](
|
|
||||||
globalBucketS3APICredentialsSecretID,
|
|
||||||
)
|
|
||||||
)
|
|
@ -2,6 +2,7 @@ package secrets
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
@ -64,3 +65,41 @@ func MultiGet(ctx context.Context, s Store, m map[ID]any) error {
|
|||||||
}
|
}
|
||||||
return errors.Join(errs...)
|
return errors.Join(errs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Export returns a map of ID to raw payload for each ID given. An error is
|
||||||
|
// returned for _each_ ID which could not be exported, wrapped using
|
||||||
|
// `errors.Join`, alongside whatever keys could be exported.
|
||||||
|
func Export(
|
||||||
|
ctx context.Context, s Store, ids []ID,
|
||||||
|
) (
|
||||||
|
map[ID]json.RawMessage, error,
|
||||||
|
) {
|
||||||
|
var (
|
||||||
|
m = map[ID]json.RawMessage{}
|
||||||
|
errs []error
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, id := range ids {
|
||||||
|
var into json.RawMessage
|
||||||
|
if err := s.Get(ctx, &into, id); err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("exporting %q: %w", id, err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m[id] = into
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, errors.Join(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import sets all given ID/payload pairs into the Store.
|
||||||
|
func Import(
|
||||||
|
ctx context.Context, s Store, m map[ID]json.RawMessage,
|
||||||
|
) error {
|
||||||
|
var errs []error
|
||||||
|
for id, payload := range m {
|
||||||
|
if err := s.Set(ctx, id, payload); err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("importing %q: %w", id, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.Join(errs...)
|
||||||
|
}
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
# shellcheck source=../../utils/with-1-data-1-empty-node-network.sh
|
|
||||||
source "$UTILS"/with-1-data-1-empty-node-network.sh
|
|
||||||
|
|
||||||
adminBS="$XDG_STATE_HOME"/isle/bootstrap.json
|
|
||||||
bs="$secondus_bootstrap" # set in with-1-data-1-empty-node-network.sh
|
|
||||||
|
|
||||||
[ "$(jq -r <"$bs" '.AdminCreationParams')" = "$(jq -r <admin.json '.CreationParams')" ]
|
|
||||||
[ "$(jq -r <"$bs" '.SignedHostAssigned.Body.Name')" = "secondus" ]
|
|
||||||
|
|
||||||
[ "$(jq -r <"$bs" '.Hosts.primus.PublicCredentials')" \
|
|
||||||
= "$(jq -r <"$adminBS" '.SignedHostAssigned.Body.PublicCredentials')" ]
|
|
||||||
|
|
||||||
[ "$(jq <"$bs" '.Hosts.primus.Garage.Instances|length')" = "3" ]
|
|
||||||
|
|
16
tests/cases/hosts/01-create.sh
Normal file
16
tests/cases/hosts/01-create.sh
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# shellcheck source=../../utils/with-1-data-1-empty-node-network.sh
|
||||||
|
source "$UTILS"/with-1-data-1-empty-node-network.sh
|
||||||
|
|
||||||
|
adminBS="$XDG_STATE_HOME"/isle/bootstrap.json
|
||||||
|
bs="$secondus_bootstrap" # set in with-1-data-1-empty-node-network.sh
|
||||||
|
|
||||||
|
[ "$(jq -r <"$bs" '.Bootstrap.AdminCreationParams')" = "$(jq -r <admin.json '.CreationParams')" ]
|
||||||
|
[ "$(jq -r <"$bs" '.Bootstrap.SignedHostAssigned.Body.Name')" = "secondus" ]
|
||||||
|
|
||||||
|
[ "$(jq -r <"$bs" '.Bootstrap.Hosts.primus.PublicCredentials')" \
|
||||||
|
= "$(jq -r <"$adminBS" '.SignedHostAssigned.Body.PublicCredentials')" ]
|
||||||
|
|
||||||
|
[ "$(jq <"$bs" '.Bootstrap.Hosts.primus.Garage.Instances|length')" = "3" ]
|
||||||
|
|
||||||
|
[ "$(jq <"$bs" '.Secrets["garage-rpc-secret"]')" != "null" ]
|
||||||
|
|
Loading…
Reference in New Issue
Block a user