Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 42 additions & 45 deletions cmd/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ package cmd
import (
"corteca/internal/configuration"
"corteca/internal/device"
_ "corteca/internal/device/cwmp"
_ "corteca/internal/device/ssh"
"corteca/internal/platform"
"corteca/internal/tui"
"fmt"
"path/filepath"
"io"
"os"
"strings"

"github.com/spf13/cobra"
Expand All @@ -26,73 +29,67 @@ var logFile string
var publishTargetName string

func init() {
execCmd.PersistentFlags().StringVarP(&specifiedArtifact, "artifact", "a", "", "Specify an artifact in the form of 'architecture:imagetype:/path/to/file', architecture=(aarch64|armv7l|x86_64), imagetype=(rootfs|oci)")
execCmd.RegisterFlagCompletionFunc("artifact", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return []string{"tar.gz, tar"}, cobra.ShellCompDirectiveFilterFileExt
})
rootCmd.AddCommand(execCmd)
execCmd.PersistentFlags().StringVar(&logFile, "logfile", platform.DefaultLog, "Specify where SSH logs will be stored")
execCmd.PersistentFlags().StringVar(&publishTargetName, "publish", "", "Publish application artifact to specified target")
execCmd.PersistentFlags().StringVarP(&artifact, "artifact", "a", "", "Specify the path to a an artifact to publish")
execCmd.PersistentFlags().BoolVar(&skipLocalConfig, "global", false, "Affect global config & ignore any project-local configuration")
}

func doExecSequence(sequence, deviceName string) {
if _, exists := config.Sequences[sequence]; !exists {
failOperation(fmt.Sprintf("Sequence '%s' not supported yet", sequence))
func doExecSequence(sequencename, deviceName string) {
if devConfig, found := config.Devices[deviceName]; !found {
failOperation(fmt.Sprintf("no config for device '%s' was found", deviceName))
} else {
configuration.GetCmdContext().Device.DeviceConfig = devConfig
configuration.GetCmdContext().Device.Name = deviceName
configuration.GetCmdContext().Arch = configuration.GetCmdContext().Device.Architecture
}

requireBuildArtifact()
var found bool
configuration.GetCmdContext().Device.Name = deviceName
configuration.GetCmdContext().Device.DeviceConfig, found = config.Devices[deviceName]
configuration.GetCmdContext().Arch = configuration.GetCmdContext().Device.Architecture
if !found {
failOperation(fmt.Sprintf("device '%s' not found", deviceName))
}

// connect to the device console
dev, err := device.NewDevice(configuration.GetCmdContext().Device.Endpoint, logFile)
if err != nil {
failOperation(fmt.Sprintf("could not create device %s", deviceName))
}
dispatcher, err := dev.Connect()
assertOperation("connecting to device", err)
defer dev.Close()

if publishTargetName != "" {
if dev.GetProtocol() == device.ConnectionSSH {
containerType := device.DetectContainerFramework(dispatcher)
if containerType == "" {
failOperation("no valid container framework found on device")
}
configuration.GetCmdContext().Build.Options.OutputType = containerType
// prepare log file
var log io.WriteCloser
switch strings.ToLower(logFile) {
case "stdout":
log = os.Stdout
case "stderr":
log = os.Stderr
default:
if f, err := os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666); err != nil {
failOperation(fmt.Sprintf("Could not create log file: %s", err.Error()))
} else {
configuration.GetCmdContext().Build.Options.OutputType = "oci"
log = f
defer func() {
if err := f.Close(); err != nil {
tui.LogError("could not close log file (%s)", err.Error())
}
}()
}
}

artifactKey := fmt.Sprintf("%s-%s", configuration.GetCmdContext().Arch, configuration.GetCmdContext().Build.Options.OutputType)
buildArtifact, ok := configuration.GetCmdContext().BuildArtifacts[artifactKey]
if !ok {
failOperation(fmt.Sprintf("no build artifact present for target architecture \"%s\"", configuration.GetCmdContext().Arch))
// connect to the device console
device, err := device.NewDevice(&configuration.GetCmdContext().Device.DeviceConfig, log)
if err != nil {
failOperation(fmt.Sprintf("could not create device %s (%s)", deviceName, err.Error()))
}
tui.LogNormal("Selected device '%s', protocol: %s", deviceName, device.GetProtocol())
defer device.Close()

configuration.GetCmdContext().BuildArtifact = filepath.Base(buildArtifact)
configuration.GetCmdContext().Publish.PublishTarget = config.Publish[publishTargetName]
configuration.GetCmdContext().Publish.Name = publishTargetName
// publish build artifact(s) if a publish target has been specified in the deploy source
if publishTargetName != "" {
tui.LogNormal("Publishing \"%s\" artifact to \"%s\"", configuration.GetCmdContext().Arch, configuration.GetCmdContext().Publish.Name)
doPublishApp(configuration.GetCmdContext().Publish.Name, configuration.GetCmdContext().Arch, false)
configuration.GetCmdContext().Publish.PublishTarget = config.Publish[publishTargetName]
configuration.GetCmdContext().Publish.Name = publishTargetName
tui.LogNormal("Publishing artifact to '%s'", configuration.GetCmdContext().Publish.Name)
doPublishApp(configuration.GetCmdContext().Publish.Name, false)
}

// execute the sequence
tui.LogNormal("Deploying %s...", buildArtifact)
if err = config.Sequences.Execute(dispatcher, sequence); err != nil {
tui.LogError("Error while %v: %v", "executing "+sequence+" sequence", err.Error())
return
if err = config.Sequences.Execute(device, sequencename); err != nil {
tui.LogError("Error while executing sequence '%s': %s", sequencename, err.Error())
} else {
tui.DisplaySuccessMsg("Sequence completed successfully!")
}
tui.DisplaySuccessMsg("Sequence completed successfully!")
}

func validExecArgsFunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
Expand Down
182 changes: 87 additions & 95 deletions cmd/publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import (
"context"
"corteca/internal/configuration"
"corteca/internal/publish"
"corteca/internal/tui"
"fmt"
"net"
"net/http"
"net/url"
"os"
"os/signal"
Expand All @@ -26,149 +28,139 @@ const (
)

var publishCmd = &cobra.Command{
Use: "publish TARGET [ARCH]",
Use: "publish TARGET",
Short: "Publish application artifact(s) to specified target, optionally filtering by architecture.",
Long: "Publish application artifact(s) to specified target, optionally filtering by architecture.",
Example: "",
Args: cobra.RangeArgs(1, 2),
Args: cobra.ExactArgs(1),
ValidArgsFunction: validPublishArgsFunc,
Run: func(cmd *cobra.Command, args []string) {
targetName := args[0]
arch := ""

if len(args) > 1 {
arch = args[1]
}

doPublishApp(targetName, arch, true)
doPublishApp(targetName, true)
},
}

type RegistryConfig struct {
configuration.HttpServerEndpoint `yaml:",inline"`
Namespace configuration.TemplateField `yaml:"namespace"`
Reference configuration.TemplateField `yaml:"reference"`
}

func init() {
publishCmd.PersistentFlags().BoolVar(&skipLocalConfig, "global", false, "Affect global config & ignore any project-local configuration")
publishCmd.PersistentFlags().StringVarP(&specifiedArtifact, "artifact", "a", "", "Specify an artifact in the form of '[ARCH]:imagetype:/path/to/file', architecture=(aarch64|armv7l|x86_64), imagetype=(rootfs|oci)")
publishCmd.PersistentFlags().StringVarP(&artifact, "artifact", "a", "", "Specify the path to a an artifact to publish")
publishCmd.RegisterFlagCompletionFunc("artifact", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return []string{"tar.gz"}, cobra.ShellCompDirectiveFilterFileExt
})
rootCmd.AddCommand(publishCmd)
}

func doPublishApp(targetName string, arch string, wait bool) {
func doPublishApp(targetName string, wait bool) {
requireBuildArtifact()
if specifiedArtifact != "" {
if arch != "" && configuration.GetCmdContext().Arch != arch {
fmt.Printf("Warning: differing architectures [%s,%s] were specified!\nPublishing %s...", arch, configuration.GetCmdContext().Arch, configuration.GetCmdContext().Arch)
}
arch = configuration.GetCmdContext().Arch
}

target, found := config.Publish[targetName]
if !found {
failOperation(fmt.Sprintf("publish target '%s' not found", targetName))
}

switch target.Method {
case configuration.PUBLISH_METHOD_LISTEN:
handlePublishMethodListen(target, wait)
case configuration.PUBLISH_METHOD_PUT:
handlePublishMethodPut(target, arch)
case configuration.PUBLISH_METHOD_COPY:
failOperation("not implemented yet")
case configuration.PUBLISH_METHOD_PUSH:
handlePublishMethodPush(target, arch)
case configuration.PUBLISH_METHOD_REGISTRY:
handlePublishMethodRegistry(target, arch, wait)
case "listen":
serverConfig := configuration.HttpServerEndpoint{}
target.Decode(&serverConfig)
handleListen(serverConfig, wait)
case "put":
clientConfig := configuration.HttpClientEndpoint{}
target.Decode(&clientConfig)
handlePut(clientConfig, artifact)
case "push":
clientConfig := configuration.HttpClientEndpoint{}
target.Decode(&clientConfig)
handlePush(clientConfig, artifact)
case "registry-v2":
registryConfig := RegistryConfig{}
target.Decode(&registryConfig)
handleRegistry(registryConfig, wait)
default:
failOperation(fmt.Sprintf("unknown publish method %v", target.Method))
failOperation(fmt.Sprintf("unknown publish method '%v'", target.Method))
}
}

func handlePublishMethodListen(target configuration.PublishTarget, wait bool) {
doListen(target, wait)
}

func handlePublishMethodRegistry(target configuration.PublishTarget, arch string, wait bool) {
artifact, found := getArtifact(arch, ociSuffix)
if !found {
failOperation(fmt.Sprintf(artifactNotFoundMessage, arch, ociSuffix))
}

registryURL, err := url.Parse(target.Addr.String())
assertOperation("parsing registry url", err)

hostPort := net.JoinHostPort(registryURL.Hostname(), registryURL.Port())
registryServer, err := publish.StartRegistry(hostPort, artifact)
if err != nil {
failOperation(fmt.Sprintf("failed to start local registry: %v", err))
}

if registryURL.Hostname() == "0.0.0.0" {
registryURL.Host = net.JoinHostPort("127.0.0.1", registryURL.Port())
}

err = publish.PushImage(artifact, registryURL, "", false)
assertOperation(fmt.Sprintf("pushing image %s to registry", artifact), err)
func handleListen(target configuration.HttpServerEndpoint, wait bool) {
u, err := url.Parse(target.Addr.String())
assertOperation("parsing target url", err)

serverRoot := distFolder
srv, err := publish.ListenAsync(serverRoot, u)
assertOperation("starting server", err)
if wait {
waitForInterruptSignal()
if err := registryServer.Shutdown(context.Background()); err != nil {
fmt.Printf("failed to shutdown registry server: %v", err)
}
srv.Shutdown(context.Background())
} else {
fmt.Printf("Serving %v on %v\n", hostPort, registryURL.String())
fmt.Printf("Serving %v on %v\n", serverRoot, u.String())
}
}

func handlePublishMethodPut(target configuration.PublishTarget, arch string) {
artifact, found := getArtifact(arch, rootfsSuffix)
if !found {
failOperation(fmt.Sprintf(artifactNotFoundMessage, arch, rootfsSuffix))
}
url, err := publish.AuthenticateHttp(target.Endpoint)
func handlePut(target configuration.HttpClientEndpoint, artifact string) {
// TODO: replace this with target.NewHttpClient() method
url, err := publish.AuthenticateHttp(target)
assertOperation("performing http authentication", err)
doPut(artifact, url, target.Token.String())
}

func handlePublishMethodPush(target configuration.PublishTarget, arch string) {
artifact, found := getArtifact(arch, ociSuffix)
if !found {
failOperation(fmt.Sprintf(artifactNotFoundMessage, arch, ociSuffix))
if err := publish.HttpPut(artifact, *url, target.Token.String()); err != nil {
assertOperation(fmt.Sprintf("while uploading file \"%s\" with HTTP(S) PUT", artifact), err)
}
url, err := publish.AuthenticateHttp(target.Endpoint)
assertOperation("performing http authentication", err)

doPush(artifact, url, target.Token.String())
}

func doPush(artifact string, url *url.URL, token string) {
err = publish.PushImage(artifact, url, token, true)
func handlePush(target configuration.HttpClientEndpoint, artifact string) {
err = publish.PushImage(artifact, &target, true)
assertOperation(fmt.Sprintf("pushing image %s to registry", artifact), err)
}

func getArtifact(arch, suffix string) (string, bool) {
artifactKey := fmt.Sprintf("%s-%s", arch, suffix)
artifactFilename, found := configuration.GetCmdContext().BuildArtifacts[artifactKey]
return artifactFilename, found
func connectableServerURL(server *http.Server) (*url.URL, error) {
u := url.URL{}
// determine schema
if server.TLSConfig != nil {
u.Scheme = "https"
} else {
u.Scheme = "http"
}
// determine host
host, port, err := net.SplitHostPort(server.Addr)
if err != nil {
return nil, fmt.Errorf("cannot determine host/port of server address '%s'", server.Addr)
}
switch host {
case "0.0.0.0", "localhost":
u.Host = net.JoinHostPort("127.0.0.1", port)
default:
u.Host = net.JoinHostPort(host, port)
}
return &u, nil
}

func doListen(target configuration.PublishTarget, wait bool) {
u, err := url.Parse(target.Addr.String())
assertOperation("parsing target url", err)
func handleRegistry(config RegistryConfig, wait bool) {
registryServer, err := publish.StartRegistry(config.HttpServerEndpoint)
if err != nil {
failOperation(fmt.Sprintf("failed to start local registry: %v", err))
}

serverRoot := distFolder
srv, err := publish.ListenAsync(serverRoot, u)
assertOperation("starting server", err)
if wait {
waitForInterruptSignal()
srv.Shutdown(context.Background())
if url, err := connectableServerURL(registryServer); err != nil {
failOperation(err.Error())
} else {
fmt.Printf("Serving %v on %v\n", serverRoot, u.String())
url.Path = fmt.Sprintf("/%s:%s", config.Namespace.String(), config.Reference.String())
tui.LogNormal("Publishing artifact on '%s'", url.String())
ep := configuration.Endpoint{Addr: configuration.T(url.String())}
err = publish.PushImage(artifact, &configuration.HttpClientEndpoint{
Endpoint: ep,
SkipTLSVerification: true,
}, false)
assertOperation(fmt.Sprintf("pushing image %s to registry", artifact), err)
}
}

func doPut(artifact string, url *url.URL, token string) {
if err := publish.HttpPut(artifact, *url, token); err != nil {
assertOperation(fmt.Sprintf("while uploading file \"%s\" with HTTP(S) PUT", artifact), err)
if wait {
waitForInterruptSignal()
if err := registryServer.Shutdown(context.Background()); err != nil {
fmt.Printf("failed to shutdown registry server: %v", err)
}
} else {
fmt.Printf("Serving on %v...\n", registryServer.Addr)
}
}

Expand Down
Loading