Skip to content

Commit

Permalink
Merge pull request #146 from choria-io/145
Browse files Browse the repository at this point in the history
(#145) Allow binaries to be build that embed a app
  • Loading branch information
ripienaar authored Aug 31, 2023
2 parents 50cb63b + 86613e4 commit b8ffca0
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 19 deletions.
26 changes: 15 additions & 11 deletions builder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,16 +324,7 @@ func (b *AppBuilder) HasDefinition() bool {
return fileExist(source)
}

func (b *AppBuilder) loadDefinition(source string) (*Definition, error) {
if b.log != nil {
b.log.Debugf("Loading application definition %v", source)
}

cfg, err := os.ReadFile(source)
if err != nil {
return nil, err
}

func (b *AppBuilder) loadDefinitionBytes(cfg []byte, path string) (*Definition, error) {
d := &Definition{}
cfgj, err := yaml.YAMLToJSON(cfg)
if err != nil {
Expand All @@ -350,11 +341,24 @@ func (b *AppBuilder) loadDefinition(source string) (*Definition, error) {
return nil, err
}

b.definitionPath = source
b.definitionPath = path

return d, nil
}

func (b *AppBuilder) loadDefinition(source string) (*Definition, error) {
if b.log != nil {
b.log.Debugf("Loading application definition %v", source)
}

cfg, err := os.ReadFile(source)
if err != nil {
return nil, err
}

return b.loadDefinitionBytes(cfg, source)
}

// LoadDefinition loads the definition for the name from file, creates the command structure and validates everything
func (b *AppBuilder) LoadDefinition() (*Definition, error) {
name := appDefPattern
Expand Down
18 changes: 17 additions & 1 deletion builder/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ import (
"context"
"errors"
"fmt"
"github.com/choria-io/fisk"
"os"
"os/signal"
"strings"
"syscall"
"time"

"github.com/choria-io/fisk"

"github.com/sirupsen/logrus"
)

Expand Down Expand Up @@ -71,6 +72,21 @@ func RunBuilderCLI(ctx context.Context, watchInterrupts bool, opts ...Option) er
return bldr.RunBuilderCLI()
}

// MountAsCommand takes the given definition and mounts it on app using name
func MountAsCommand(ctx context.Context, app KingpinCommand, definition []byte, log Logger) error {
bldr, err := createBuilder(ctx, "builder", log, WithAppDefinitionBytes(definition))
if err != nil {
return err
}

bldr.cfg, err = bldr.LoadConfig()
if err != nil && !errors.Is(err, ErrConfigNotFound) {
return err
}

return bldr.registerCommands(app, bldr.def.commands...)
}

// RunStandardCLI runs a standard command line instance with shutdown watchers etc. If log is nil a logger will be created
func RunStandardCLI(ctx context.Context, name string, watchInterrupts bool, log Logger, opts ...Option) error {
ctx, cancel := context.WithCancel(ctx)
Expand Down
9 changes: 9 additions & 0 deletions builder/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ func WithLogger(logger Logger) Option {
}
}

// WithAppDefinitionBytes uses a provided app definition rather than load one from disk
func WithAppDefinitionBytes(def []byte) Option {
return func(b *AppBuilder) (err error) {
b.def, err = b.loadDefinitionBytes(def, "embedded")

return err
}
}

// WithAppDefinitionFile sets a file where the definition should be loaded from
func WithAppDefinitionFile(f string) Option {
return func(b *AppBuilder) error {
Expand Down
17 changes: 17 additions & 0 deletions commands/commands.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) 2023, R.I. Pienaar and the Choria Project contributors
//
// SPDX-License-Identifier: Apache-2.0

package commands

import (
"github.com/choria-io/appbuilder/commands/exec"
"github.com/choria-io/appbuilder/commands/parent"
"github.com/choria-io/appbuilder/commands/scaffold"
)

func MustRegisterStandardCommands() {
parent.MustRegister()
exec.MustRegister()
scaffold.MustRegister()
}
62 changes: 62 additions & 0 deletions docs/content/experiments/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,65 @@ An example can be found in the source repository for this project.

Configuration is looked for in the local directory in the `.abtenv` file. At present this is not searched for in parent
directories.

## Compiled Applications

It's nice that you do not need to compile App Builder apps into binaries as it allows for fast iteration, but sometimes
it might be desired.

As of version `0.7.2` we support compiling binaries that contain an application.

Given an application in `app.yaml` we can create a small go stub:

```go
package main

import (
"context"
_ "embed"
"os"

"github.com/choria-io/appbuilder/builder"
"github.com/choria-io/fisk"
)

//go:embed app.yaml
var def []byte

func main() {
builder.MustRegisterStandardCommands()

cmd := fisk.Newf("myapp", "My compiled App Builder application")

err := builder.MountAsCommand(context.TODO(), cmd, def, nil)
if err != nil {
panic(err)
}

cmd.MustParseWithUsage(os.Args[1:])
}
```

When you compile this as a normal Go application your binary will be an executable version of the app.

Here we mount the application at the top level of the `myapp` binary, but you could also mount it later on - perhaps you
have other compiled in behaviours you wish to surface:

```go
func main() {
builder.MustRegisterStandardCommands()

cmd := fisk.Newf("myapp", "My compiled App Builder application")
embedded := cmd.Command("embedded","Embedded application goes here")

err := builder.MountAsCommand(context.TODO(), embedded, def, nil)
if err != nil {
panic(err)
}

cmd.MustParseWithUsage(os.Args[1:])
}
```

Here we would end up with `myapp embedded [app commands]` - the command being mounted at a deeper level in the resulting
compiled application. This way you can plug a App Builder command into any level programmatically.
10 changes: 3 additions & 7 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,16 @@ import (
"strings"

"github.com/choria-io/appbuilder/builder"
"github.com/choria-io/appbuilder/commands/exec"
"github.com/choria-io/appbuilder/commands/parent"
"github.com/choria-io/appbuilder/commands/scaffold"
"github.com/choria-io/appbuilder/commands"
)

func main() {
parent.MustRegister()
exec.MustRegister()
scaffold.MustRegister()

name := filepath.Base(os.Args[0])

var err error

commands.MustRegisterStandardCommands()

if strings.HasPrefix(name, "appbuilder") {
err = builder.RunBuilderCLI(context.Background(), true, builder.WithContextualUsageOnError())
} else if strings.HasPrefix(name, "abt") {
Expand Down

0 comments on commit b8ffca0

Please sign in to comment.