Skip to content

Commit

Permalink
Allow running fablab apps using fablab executable. Add sync-binaries …
Browse files Browse the repository at this point in the history
…command. Allow using native ssh app.
  • Loading branch information
plorenz committed Jan 12, 2024
1 parent 61134cb commit 5c035d9
Show file tree
Hide file tree
Showing 8 changed files with 194 additions and 36 deletions.
39 changes: 37 additions & 2 deletions cmd/fablab/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,50 @@ package main
import (
"github.com/michaelquigley/pfxlog"
"github.com/openziti/fablab/cmd/fablab/subcmd"
"github.com/openziti/fablab/kernel/model"
"github.com/sirupsen/logrus"
"os"
"os/exec"
)

func init() {
pfxlog.GlobalInit(logrus.InfoLevel, pfxlog.DefaultOptions().SetTrimPrefix("github.com/openziti/"))
}

func main() {
if err := subcmd.Execute(); err != nil {
logrus.Fatalf("failure (%v)", err)
if len(os.Args) > 1 {
runLocalBinary := false
if os.Args[1] == "completion" {
runLocalBinary = true
} else if len(os.Args) > 2 {
if os.Args[1] == "list" && os.Args[2] == "instances" {
runLocalBinary = true
}
}

if runLocalBinary {
if err := subcmd.Execute(); err != nil {
logrus.Fatalf("failure (%v)", err)
}
return
}
}

cfg := model.GetConfig()
instance, ok := cfg.Instances[cfg.Default]
if !ok {
logrus.Fatalf("invalid default instance '%s'", cfg.Default)
return
}

if instance.Executable == "" {
logrus.Fatalf("default instance '%s' has no executable configured to delegate to", cfg.Default)
return
}

cmd := exec.Command(instance.Executable, os.Args[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stderr
_ = cmd.Run()
}
52 changes: 33 additions & 19 deletions cmd/fablab/subcmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,45 +17,59 @@
package subcmd

import (
"fmt"
"github.com/openziti/fablab/kernel/model"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"os"
"os/exec"
)

func init() {
createCmd := NewCreateCommand()

createCmd.Flags().StringVarP(&createCmd.Name, "name", "n", "", "name for the new instance")
createCmd.Flags().StringVarP(&createCmd.WorkingDir, "directory", "d", "", "working directory for the new instance")
createCmd.Flags().StringToStringVarP(&createCmd.Bindings, "label", "l", nil, "label bindings to include in the model")
createCmd.Flags()

RootCmd.AddCommand(createCmd.Command)
RootCmd.AddCommand(NewCreateCommand())
}

func NewCreateCommand() *CreateCommand {
result := &CreateCommand{
Command: &cobra.Command{
Use: "create <model>",
Short: "create a fablab instance from a model",
Args: cobra.MaximumNArgs(1),
},
func NewCreateCommand() *cobra.Command {
createCmd := &CreateCommand{}

cmd := &cobra.Command{
Use: "create <model>",
Short: "create a fablab instance from a model",
Args: cobra.MaximumNArgs(1),
RunE: createCmd.create,
}

result.Command.RunE = result.create
cmd.Flags().StringVarP(&createCmd.Name, "name", "n", "", "name for the new instance")
cmd.Flags().StringVarP(&createCmd.WorkingDir, "directory", "d", "", "working directory for the new instance")
cmd.Flags().StringVarP(&createCmd.Executable, "executable", "e", "",
"path to the model specific fablab executable. defaults to the current executable")
cmd.Flags().StringToStringVarP(&createCmd.Bindings, "label", "l", nil, "label bindings to include in the model")

return result
return cmd
}

type CreateCommand struct {
*cobra.Command
Name string
WorkingDir string
Bindings map[string]string
Executable string
}

func (self *CreateCommand) create(*cobra.Command, []string) error {
if self.Executable == "" {
executable, err := exec.LookPath(os.Args[0])
if err != nil {
return fmt.Errorf("unable to get path (%w)", err)
}
self.Executable = executable
}

_, err := os.Stat(self.Executable)
if os.IsNotExist(err) {
return fmt.Errorf("invalid executable path '%s' (%w)", self.Executable, err)
}

if model.GetModel() == nil {
return errors.New("no model configured, exiting")
}
Expand All @@ -64,7 +78,7 @@ func (self *CreateCommand) create(*cobra.Command, []string) error {
return errors.New("no model id provided, exiting")
}

instanceId, err := model.NewInstance(self.Name, self.WorkingDir)
instanceId, err := model.NewInstance(self.Name, self.WorkingDir, self.Executable)
if err != nil {
return errors.Wrapf(err, "unable to create instance of model %v, exiting", model.GetModel().Id)
}
Expand Down
60 changes: 52 additions & 8 deletions cmd/fablab/subcmd/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,40 @@
package subcmd

import (
"fmt"
"github.com/openziti/fablab/kernel/libssh"
"github.com/openziti/fablab/kernel/model"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"os"
"os/exec"
)

func init() {
RootCmd.AddCommand(sshCmd)
RootCmd.AddCommand(newSshCmd())
}

var sshCmd = &cobra.Command{
Use: "ssh <hostSpec>",
Short: "establish an ssh connection to the model",
Args: cobra.ExactArgs(1),
Run: ssh,
type sshCmd struct {
forceBuiltIn bool
}

func ssh(_ *cobra.Command, args []string) {
func newSshCmd() *cobra.Command {
cmd := &sshCmd{}

var cobraCmd = &cobra.Command{
Use: "ssh <hostSpec>",
Short: "establish an ssh connection to a host in the model",
Args: cobra.ExactArgs(1),
Run: cmd.ssh,
}

cobraCmd.Flags().BoolVarP(&cmd.forceBuiltIn, "force-built-in", "f", false,
"Force use of built-in ssh client, don't try and detect/use an external ssh client")

return cobraCmd
}

func (self *sshCmd) ssh(_ *cobra.Command, args []string) {
if err := model.Bootstrap(); err != nil {
logrus.Fatalf("unable to bootstrap (%s)", err)
}
Expand All @@ -45,7 +61,35 @@ func ssh(_ *cobra.Command, args []string) {
logrus.Fatalf("your regionSpec and hostSpec matched [%d] hosts. must match exactly 1", len(hosts))
}

if err := libssh.RemoteShell(hosts[0].NewSshConfigFactory()); err != nil {
sshCfg := hosts[0].NewSshConfigFactory()

if !self.forceBuiltIn {
_, err := exec.LookPath("ssh")
if err == nil {
nativeSsh(sshCfg)
return
}
}

if err := libssh.RemoteShell(sshCfg); err != nil {
logrus.Fatalf("error executing remote shell (%v)", err)
}
}

func nativeSsh(sshCfg libssh.SshConfigFactory) {
cmdArgs := []string{
"-i", sshCfg.KeyPath(),
"-o", "StrictHostKeyChecking no",
sshCfg.User() + "@" + sshCfg.Hostname(),
}

if sshCfg.Port() != 22 {
cmdArgs = append(cmdArgs, "-p", fmt.Sprintf("%v", sshCfg.Port()))
}

cmd := exec.Command("ssh", cmdArgs...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
_ = cmd.Run()
}
49 changes: 49 additions & 0 deletions cmd/fablab/subcmd/sync_binaries.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
Copyright 2019 NetFoundry Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package subcmd

import (
"github.com/openziti/fablab/kernel/model"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

func init() {
RootCmd.AddCommand(syncBinariesCmd)
}

var syncBinariesCmd = &cobra.Command{
Use: "sync-binaries",
Short: "synchronize only the binaries in a run kit onto the network",
Args: cobra.ExactArgs(0),
Run: syncBinaries,
}

func syncBinaries(_ *cobra.Command, _ []string) {
if err := model.Bootstrap(); err != nil {
logrus.Fatalf("unable to bootstrap (%s)", err)
}

ctx, err := model.NewRun()
if err != nil {
logrus.WithError(err).Fatal("error initializing run")
}
ctx.GetModel().Scope.PutVariable("sync.target", "bin")
if err := ctx.GetModel().Sync(ctx); err != nil {
logrus.Fatalf("error synchronizing all hosts (%s)", err)
}
}
24 changes: 19 additions & 5 deletions kernel/lib/runlevel/3_distribution/rsync/rsync.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,32 +209,36 @@ func (self *remoteRsyncer) run() error {
}

func synchronizeHost(config *Config) error {
if output, err := libssh.RemoteExec(config.sshConfigFactory, "mkdir -p /home/ubuntu/fablab"); err == nil {
if output, err := libssh.RemoteExec(config.sshConfigFactory, "mkdir -p /home/ubuntu/fablab/bin"); err == nil {
if output != "" {
logrus.Infof("output [%s]", strings.Trim(output, " \t\r\n"))
}
} else {
return err
}

if err := RunRsync(config, model.KitBuild()+"/", fmt.Sprintf("ubuntu@%s:/home/ubuntu/fablab", config.sshConfigFactory.Hostname())); err != nil {
extraPath := config.syncTarget

if err := RunRsync(config, model.KitBuild()+"/"+extraPath, fmt.Sprintf("ubuntu@%s:/home/ubuntu/fablab/"+extraPath, config.sshConfigFactory.Hostname())); err != nil {
return fmt.Errorf("rsyncStage failed (%w)", err)
}

return nil
}

func synchronizeHostToHost(srcConfig, dstConfig *Config) error {
if output, err := libssh.RemoteExec(dstConfig.sshConfigFactory, "mkdir -p /home/ubuntu/fablab"); err == nil {
if output, err := libssh.RemoteExec(dstConfig.sshConfigFactory, "mkdir -p /home/ubuntu/fablab/bin"); err == nil {
if output != "" {
logrus.Infof("output [%s]", strings.Trim(output, " \t\r\n"))
}
} else {
return err
}

dst := fmt.Sprintf("ubuntu@%s:/home/ubuntu/fablab/", dstConfig.sshConfigFactory.Hostname())
cmd := fmt.Sprintf("rsync -avz --delete -e 'ssh -o StrictHostKeyChecking=no' /home/ubuntu/fablab/* %v", dst)
extraPath := dstConfig.syncTarget

dst := fmt.Sprintf("ubuntu@%s:/home/ubuntu/fablab/%s", dstConfig.sshConfigFactory.Hostname(), extraPath)
cmd := fmt.Sprintf("rsync -avz --delete -e 'ssh -o StrictHostKeyChecking=no' /home/ubuntu/fablab/%v* %v", extraPath, dst)
output, err := libssh.RemoteExec(srcConfig.sshConfigFactory, cmd)
if err == nil && output != "" {
logrus.Infof("output [%s]", strings.Trim(output, " \t\r\n"))
Expand All @@ -246,6 +250,7 @@ type Config struct {
sshBin string
sshConfigFactory libssh.SshConfigFactory
rsyncBin string
syncTarget string
}

func NewConfig(h *model.Host) *Config {
Expand All @@ -255,6 +260,15 @@ func NewConfig(h *model.Host) *Config {
rsyncBin: h.GetStringVariableOr("distribution.rsync_bin", "rsync"),
}

config.syncTarget = h.GetStringVariableOr("sync.target", "all")
if config.syncTarget == "all" {
config.syncTarget = ""
}

if config.syncTarget != "" && !strings.HasSuffix(config.syncTarget, "/") {
config.syncTarget += "/"
}

return config
}

Expand Down
3 changes: 2 additions & 1 deletion kernel/model/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
"path/filepath"
)

func NewInstance(id, workingDirectory string) (string, error) {
func NewInstance(id, workingDirectory, executable string) (string, error) {
cfg := GetConfig()

if id == "" {
Expand Down Expand Up @@ -62,6 +62,7 @@ func NewInstance(id, workingDirectory string) (string, error) {
Id: id,
Model: model.Id,
WorkingDirectory: workingDirectory,
Executable: executable,
}

cfg.Instances[id] = instanceConfig
Expand Down
1 change: 1 addition & 0 deletions kernel/model/instances.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type InstanceConfig struct {
Id string `yaml:"name"`
Model string `yaml:"model"`
WorkingDirectory string `yaml:"working_directory"`
Executable string `yaml:"executable"`
}

func (self *InstanceConfig) CleanupWorkingDir() error {
Expand Down
2 changes: 1 addition & 1 deletion kernel/model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,7 @@ func (host *Host) ExecLogged(cmds ...string) (string, error) {

func (host *Host) ExecLogOnlyOnError(cmds ...string) error {
if o, err := host.ExecLogged(cmds...); err != nil {
logrus.Errorf("output [%s]", o)
logrus.WithField("hostId", host.Id).Errorf("output [%s]", o)
return fmt.Errorf("error executing process on [%s] (%s)", host.PublicIp, err)
}
return nil
Expand Down

0 comments on commit 5c035d9

Please sign in to comment.