Compare commits

..

2 Commits

Author SHA1 Message Date
Jeff McCune
5860c5747b (#87) generate sub-command with embedded platform
This patch adds a generate subcommand that copies a platform embedded
into the executable to the local filesystem.  The purpose is to
accelerate initial setup with canned example platforms.

Two platforms are intended to start, one bare and one reference
platform.  The number of platforms embedded into holos should be kept
small (2-3) to limit our support burden.
2024-05-14 15:03:21 -07:00
Jeff McCune
d3c2d55706 (#172) Deploy v0.76.0 to dev 2024-05-14 13:28:19 -07:00
82 changed files with 150 additions and 6 deletions

1
.gitignore vendored
View File

@@ -6,3 +6,4 @@ coverage.out
*.hold/
/deploy/
.vscode/
tmp/

View File

@@ -34,7 +34,7 @@ let OBJECTS = #APIObjects & {
containers: [
{
name: Holos
image: "271053619184.dkr.ecr.us-east-2.amazonaws.com/holos-run/holos-server/holos:0.74.0"
image: "271053619184.dkr.ecr.us-east-2.amazonaws.com/holos-run/holos-server/holos:v0.76.0"
imagePullPolicy: "Always"
env: [
{

View File

@@ -0,0 +1,43 @@
package generate
import (
"fmt"
"strings"
"github.com/holos-run/holos/internal/cli/command"
"github.com/holos-run/holos/internal/errors"
"github.com/holos-run/holos/internal/generate"
"github.com/holos-run/holos/internal/holos"
"github.com/spf13/cobra"
)
// New returns a new generate command.
func New(cfg *holos.Config) *cobra.Command {
cmd := command.New("generate")
cmd.Aliases = []string{"gen"}
cmd.Short = "generate local resources"
cmd.Args = cobra.NoArgs
cmd.AddCommand(NewPlatform(cfg))
return cmd
}
func NewPlatform(cfg *holos.Config) *cobra.Command {
cmd := command.New("platform")
cmd.Use = "platform [flags] PLATFORM"
cmd.Short = "generate a platform from an embedded schematic"
cmd.Long = fmt.Sprintf("Embedded platforms available to generate:\n\n %s", strings.Join(generate.Platforms(), "\n "))
cmd.Args = cobra.ExactArgs(1)
cmd.RunE = func(cmd *cobra.Command, args []string) error {
ctx := cmd.Root().Context()
for _, name := range args {
if err := generate.GeneratePlatform(ctx, name); err != nil {
return errors.Wrap(err)
}
}
return nil
}
return cmd
}

View File

@@ -5,11 +5,16 @@ import (
"github.com/spf13/cobra"
"github.com/holos-run/holos/version"
"github.com/holos-run/holos/internal/holos"
"github.com/holos-run/holos/internal/logger"
"github.com/holos-run/holos/internal/server"
"github.com/holos-run/holos/internal/cli/build"
"github.com/holos-run/holos/internal/cli/controller"
"github.com/holos-run/holos/internal/cli/create"
"github.com/holos-run/holos/internal/cli/generate"
"github.com/holos-run/holos/internal/cli/get"
"github.com/holos-run/holos/internal/cli/kv"
"github.com/holos-run/holos/internal/cli/login"
@@ -19,9 +24,6 @@ import (
"github.com/holos-run/holos/internal/cli/rpc"
"github.com/holos-run/holos/internal/cli/token"
"github.com/holos-run/holos/internal/cli/txtar"
"github.com/holos-run/holos/internal/holos"
"github.com/holos-run/holos/internal/logger"
"github.com/holos-run/holos/version"
)
// New returns a new root *cobra.Command for command line execution.
@@ -41,7 +43,7 @@ func New(cfg *holos.Config) *cobra.Command {
return err
}
log := cfg.Logger()
c.SetContext(logger.NewContext(c.Context(), log))
c.Root().SetContext(logger.NewContext(c.Context(), log))
// Set the default logger after flag parsing.
slog.SetDefault(log)
return nil
@@ -65,6 +67,7 @@ func New(cfg *holos.Config) *cobra.Command {
rootCmd.AddCommand(logout.New(cfg))
rootCmd.AddCommand(token.New(cfg))
rootCmd.AddCommand(rpc.New(cfg))
rootCmd.AddCommand(generate.New(cfg))
// Maybe not needed?
rootCmd.AddCommand(txtar.New(cfg))

View File

@@ -0,0 +1,97 @@
package generate
import (
"context"
"embed"
"fmt"
"io/fs"
"os"
"path/filepath"
"github.com/holos-run/holos/internal/errors"
"github.com/holos-run/holos/internal/server/middleware/logger"
)
//go:embed all:platforms
var platforms embed.FS
// root is the root path to copy platform cue code from.
const root = "platforms"
// Platforms returns a slice of embedded platforms or nil if there are none.
func Platforms() []string {
entries, err := fs.ReadDir(platforms, root)
if err != nil {
return nil
}
dirs := make([]string, 0, len(entries))
for _, entry := range entries {
if entry.IsDir() && entry.Name() != "cue.mod" {
dirs = append(dirs, entry.Name())
}
}
return dirs
}
// GeneratePlatform writes the cue code for a platform to the local working
// directory.
func GeneratePlatform(ctx context.Context, name string) error {
// Check for a valid platform
platformPath := filepath.Join(root, name)
if !dirExists(platforms, platformPath) {
return errors.Wrap(fmt.Errorf("cannot generate: have: [%s] want: %+v", name, Platforms()))
}
// Copy the cue.mod directory
if err := copyEmbedFS(ctx, platforms, filepath.Join(root, "cue.mod"), "cue.mod"); err != nil {
return errors.Wrap(err)
}
// Copy the named platform
if err := copyEmbedFS(ctx, platforms, platformPath, "."); err != nil {
return errors.Wrap(err)
}
return nil
}
func dirExists(srcFS embed.FS, path string) bool {
entries, err := fs.ReadDir(srcFS, path)
if err != nil {
return false
}
return len(entries) > 0
}
func copyEmbedFS(ctx context.Context, srcFS embed.FS, srcPath, dstPath string) error {
log := logger.FromContext(ctx)
return fs.WalkDir(srcFS, srcPath, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return errors.Wrap(err)
}
relPath, err := filepath.Rel(srcPath, path)
if err != nil {
return errors.Wrap(err)
}
dstFullPath := filepath.Join(dstPath, relPath)
if d.IsDir() {
if err := os.MkdirAll(dstFullPath, os.ModePerm); err != nil {
return errors.Wrap(err)
}
log.DebugContext(ctx, "created", "directory", dstFullPath)
} else {
data, err := srcFS.ReadFile(path)
if err != nil {
return errors.Wrap(err)
}
if err := os.WriteFile(dstFullPath, data, os.ModePerm); err != nil {
return errors.Wrap(err)
}
log.DebugContext(ctx, "wrote", "file", dstFullPath)
}
return nil
})
}

View File

@@ -1 +1 @@
0
1