Skip to content
Open
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
46 changes: 23 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<!-- markdownlint-disable MD033 MD041-->
<div align="center">
<img src="./doc/images/nokia_logo_blue.svg" width="250" alt="Nokia Logo" title="Nokia Logo" />
</div>
<!-- markdownlint-enable MD033 MD041-->

# Corteca Developer Toolkit

Expand Down Expand Up @@ -69,16 +71,14 @@ supported:
Devices and the sequences to run on them are configured in `corteca.yaml` and
can be targeted by name when running `corteca exec`.

<hr>

## Prerequisites

| Requirement | Version | Notes |
| ----------- | -------- | ---------------------------------------------- |
| Go | ≥ 1.21 | Required to build from source |
| Docker | ≥ 23.0 | Required to build application container images |
| Docker BuildKit | ≥ 0.11 | Required for `docker build --output` |
| make | any | Used to drive the build and install targets |
| Requirement | Version | Notes |
|------------------|---------|------------------------------------------------|
| Go | ≥ 1.21 | Required to build from source |
| Docker | ≥ 23.0 | Required to build application container images |
| Docker BuildKit | ≥ 0.11 | Required for `docker build --output` |
| make | any | Used to drive the build and install targets |

## Build

Expand All @@ -87,49 +87,49 @@ can be targeted by name when running `corteca exec`.
To build locally, you need to have a Go toolchain installed. Clone the repository and run:

```bash
$ make
make
```

The compiled binary is placed in the `dist/` directory. You can also build packages for your specific platform. The available `make` targets are `deb`, `rpm`, `osx` and `msix`:

```bash
$ make rpm
make rpm
```

### Build using Docker

If you do not have a local Go toolchain, you can build entirely inside Docker (BuildKit is required). The following command builds all supported packages:

```bash
$ docker build --output ./dist .
docker build --output ./dist .
```

#### Selecting the Packages to Build

By default, packages are created for all architectures (`arm64` and `amd64`). You can select the architectires to build for by setting the `ARCH` build-time variable to the space-separated list of architectures to build for. For example, to only build packages for the `amd64` architecture run:

```bash
$ docker build --build-arg ARCH="amd64" --output ./dist .
docker build --build-arg ARCH="amd64" --output ./dist .
```

The package types created by default are `deb`, `rpm`, `osx` and `msix`. To only build selected package types, set the `PACKAGE` build-time variable to the space-separated list of package types to build. The following command only builds `deb` and `rpm` packages on all architectures:

```bash
$ docker build --build-arg PACKAGE="deb rpm" --output ./dist .
docker build --build-arg PACKAGE="deb rpm" --output ./dist .
```

By setting both the `ARCH` and `PACKAGE` build-time variables, you can further limit the build scope to specific packages. For example, to only build the `deb` package for the `amd64` architecture, run:

```bash
$ docker build --build-arg ARCH="amd64" --build-arg PACKAGE="deb" --output ./dist .
docker build --build-arg ARCH="amd64" --build-arg PACKAGE="deb" --output ./dist .
```

#### Installing Docker BuildKit

On Docker Engine < 23.0, BuildKit must be enabled manually. On Ubuntu 22.04:

```bash
$ sudo apt-get install docker-buildx-plugin
sudo apt-get install docker-buildx-plugin
```

Then either prefix each `docker build` invocation with `DOCKER_BUILDKIT=1`, or
Expand All @@ -145,13 +145,13 @@ the `/usr/bin` folder and the default configuration files to the `/etc/corteca`
folder:

```bash
$ sudo make install
sudo make install
```

To remove a previous manual installation

```bash
$ sudo make uninstall
sudo make uninstall
```

You can customize the destination folder by overriding the `$DESTDIR`
Expand All @@ -167,8 +167,8 @@ $ DESTDIR=~/.local/share make install
If you are using debian/ubuntu or redhat-based distributions, you can create a relevant package and let your package manager handle installation. E.g. for ubuntu:

```bash
$ make deb
$ make rpm
make deb
make rpm
```

## Getting Started
Expand All @@ -177,10 +177,10 @@ The fastest way to get up and running is to create a project, build it, and
publish it to a local registry in three commands:

```bash
$ corteca create my-app # scaffold a new application project
$ cd my-app
$ corteca build aarch64 # build an OCI image for aarch64
$ corteca publish localRegistry # push it to a local OCI registry
corteca create my-app # scaffold a new application project
cd my-app
corteca build aarch64 # build an OCI image for aarch64
corteca publish localRegistry # push it to a local OCI registry
```

For a step-by-step walkthrough — including how to configure a device target and
Expand Down
2 changes: 1 addition & 1 deletion cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func doBuildApp(selectedArchitecture string) {
if rootfsPath == "" {
assertOperation(fmt.Sprintf("building '%s'", selectedArchitecture), builder.BuildRootFS(projectRoot, rootfsBuildPath, settings, config.App, config.Build))
} else {
fsutil.ExtractTarball(rootfsPath, rootfsBuildPath)
assertOperation(fmt.Sprintf("extracting rootfs from '%s'", rootfsPath), fsutil.ExtractTarball(rootfsPath, rootfsBuildPath))
}

// STEP 2: validate rootfs
Expand Down
4 changes: 2 additions & 2 deletions cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ func doGetConfigValue(key string) {
assertOperation("retrieving config value", err)
enc := yaml.NewEncoder(os.Stdout)
enc.SetIndent(configuration.YamlIndentation)
enc.Encode(field)
assertOperation("encoding config value", enc.Encode(field))
}

func doSetConfigValue(key, value string, append bool) {
Expand Down Expand Up @@ -144,7 +144,7 @@ func doEvalConfigValue(expr string) {
field := configuration.T(expr)
enc := yaml.NewEncoder(os.Stdout)
enc.SetIndent(configuration.YamlIndentation)
enc.Encode(field.String())
assertOperation("encoding config value", enc.Encode(field.String()))
}

func validConfigArgsFunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
Expand Down
32 changes: 21 additions & 11 deletions cmd/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func doGenerateDocumentation(cmdNames []string) {
continue
}
cmdFile := createMdFile(cmd)
defer cmdFile.Close()
defer func() { _ = cmdFile.Close() }()
err = generateMarkdown(cmd, cmdFile, false)
assertOperation("generating documentation for corteca commands", err)
}
Expand All @@ -67,7 +67,7 @@ func doGenerateDocumentation(cmdNames []string) {
index--

cmdFile := createMdFile(cmd)
defer cmdFile.Close()
defer func() { _ = cmdFile.Close() }()
err = generateMarkdown(cmd, cmdFile, false)
assertOperation("generating documentation for specific corteca command(s)", err)
}
Expand All @@ -82,38 +82,48 @@ func doGenerateDocumentation(cmdNames []string) {
if len(notMatchedCmds) == 0 {
fmt.Println("Cortecacli's commands documentation has generated successfuly!")
} else {
fmt.Fprintf(os.Stdout, "The following commands are not part of cortecacli: %s\n", notMatchedCmds)
_, _ = fmt.Fprintf(os.Stdout, "The following commands are not part of cortecacli: %s\n", notMatchedCmds)
}
} else {
fmt.Fprintf(os.Stdout, "No command documentation was generated\nThe following commands are not part of cortecacli: %s\n", notMatchedCmds)
_, _ = fmt.Fprintf(os.Stdout, "No command documentation was generated\nThe following commands are not part of cortecacli: %s\n", notMatchedCmds)
}
}

func generateMarkdown(cmd *cobra.Command, cmdFile *os.File, isSubcmd bool) error {
// Write command usage
if isSubcmd {
_, writeToFileErr = cmdFile.WriteString(fmt.Sprintf("### Corteca %s %s\n\n%s\n\n", cmd.Parent().Name(), cmd.Name(), cmd.Long))
_, writeToFileErr = fmt.Fprintf(cmdFile, "### Corteca %s %s\n\n%s\n\n", cmd.Parent().Name(), cmd.Name(), cmd.Long)
} else {
_, writeToFileErr = cmdFile.WriteString(fmt.Sprintf("## Corteca %s\n\n%s\n\n", cmd.Name(), cmd.Long))
_, writeToFileErr = fmt.Fprintf(cmdFile, "## Corteca %s\n\n%s\n\n", cmd.Name(), cmd.Long)
}
if writeToFileErr != nil {
return writeToFileErr
}
cmdFile.WriteString(fmt.Sprintf("### Usage:\n\n```\n%s\n```\n\n", cmd.UseLine()))
cmdFile.WriteString(fmt.Sprintf("### Flags:\n\n```\n%s%s\n```\n\n", cmd.InheritedFlags().FlagUsages(), cmd.LocalFlags().FlagUsages()))
cmdFile.WriteString(fmt.Sprintf("### Example:\n\n```\n%s\n```\n\n", cmd.Example))
if _, err := fmt.Fprintf(cmdFile, "### Usage:\n\n```\n%s\n```\n\n", cmd.UseLine()); err != nil {
return err
}
if _, err := fmt.Fprintf(cmdFile, "### Flags:\n\n```\n%s%s\n```\n\n", cmd.InheritedFlags().FlagUsages(), cmd.LocalFlags().FlagUsages()); err != nil {
return err
}
if _, err := fmt.Fprintf(cmdFile, "### Example:\n\n```\n%s\n```\n\n", cmd.Example); err != nil {
return err
}

// Append subcommands to parent command's documentation, if any
if len(cmd.Commands()) > 0 {
cmdFile.WriteString("## Subcommands:\n\n")
if _, err := fmt.Fprintf(cmdFile, "## Subcommands:\n\n"); err != nil {
return err
}
for _, subCmd := range cmd.Commands() {
// Recursive call for nested subcommands
err := generateMarkdown(subCmd, cmdFile, true)
assertOperation("generating documentation for corteca subcommands", err)
}
}

cmdFile.WriteString("\n")
if _, err := fmt.Fprintf(cmdFile, "\n"); err != nil {
return err
}
return nil
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ var logFile string
var publishTargetName string

func init() {
execCmd.RegisterFlagCompletionFunc("artifact", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
_ = execCmd.RegisterFlagCompletionFunc("artifact", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return []string{"tar.gz, tar"}, cobra.ShellCompDirectiveFilterFileExt
})
rootCmd.AddCommand(execCmd)
Expand Down
17 changes: 6 additions & 11 deletions cmd/publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,6 @@ import (
"github.com/spf13/cobra"
)

const (
ociSuffix = "oci"
rootfsSuffix = "rootfs"
artifactNotFoundMessage = "No build artifact found for [%s,%s]"
)

var publishCmd = &cobra.Command{
Use: "publish TARGET",
Expand All @@ -49,7 +44,7 @@ type RegistryConfig struct {
func init() {
publishCmd.PersistentFlags().BoolVar(&skipLocalConfig, "global", false, "Affect global config & ignore any project-local configuration")
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) {
_ = publishCmd.RegisterFlagCompletionFunc("artifact", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return []string{"tar.gz"}, cobra.ShellCompDirectiveFilterFileExt
})
rootCmd.AddCommand(publishCmd)
Expand All @@ -65,19 +60,19 @@ func doPublishApp(targetName string, wait bool) {
switch target.Method {
case "listen":
serverConfig := configuration.HttpServerEndpoint{}
target.Decode(&serverConfig)
assertOperation("decoding publish config", target.Decode(&serverConfig))
handleListen(serverConfig, wait)
case "put":
clientConfig := configuration.HttpClientEndpoint{}
target.Decode(&clientConfig)
assertOperation("decoding publish config", target.Decode(&clientConfig))
handlePut(clientConfig, artifact)
case "push":
clientConfig := configuration.HttpClientEndpoint{}
target.Decode(&clientConfig)
assertOperation("decoding publish config", target.Decode(&clientConfig))
handlePush(clientConfig, artifact)
case "registry-v2":
registryConfig := RegistryConfig{}
target.Decode(&registryConfig)
assertOperation("decoding publish config", target.Decode(&registryConfig))
handleRegistry(registryConfig, wait)
default:
failOperation(fmt.Sprintf("unknown publish method '%v'", target.Method))
Expand All @@ -93,7 +88,7 @@ func handleListen(target configuration.HttpServerEndpoint, wait bool) {
assertOperation("starting server", err)
if wait {
waitForInterruptSignal()
srv.Shutdown(context.Background())
assertOperation("shutting down server", srv.Shutdown(context.Background()))
} else {
fmt.Printf("Serving %v on %v\n", serverRoot, u.String())
}
Expand Down
20 changes: 6 additions & 14 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,20 +61,22 @@ func init() {
rootCmd.SetVersionTemplate("{{.Short}} v{{.Version}}\n")
rootCmd.CompletionOptions.HiddenDefaultCmd = true
rootCmd.PersistentFlags().StringArrayVarP(&configOverrides, "config", "c", []string{}, "Override a configuration value in the form of a 'key=value' pair")
rootCmd.RegisterFlagCompletionFunc("config", validConfigArgsFunc)
_ = rootCmd.RegisterFlagCompletionFunc("config", validConfigArgsFunc)
rootCmd.PersistentFlags().StringVarP(&systemConfigRoot, "configRoot", "r", systemConfigRoot, "Override configuration root folder")
rootCmd.RegisterFlagCompletionFunc("configRoot", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
_ = rootCmd.RegisterFlagCompletionFunc("configRoot", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return nil, cobra.ShellCompDirectiveFilterDirs
})
rootCmd.PersistentFlags().StringVarP(&projectRoot, "projectRoot", "C", projectRoot, "Specify project root folder")
rootCmd.RegisterFlagCompletionFunc("projectRoot", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
_ = rootCmd.RegisterFlagCompletionFunc("projectRoot", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return nil, cobra.ShellCompDirectiveFilterDirs
})
rootCmd.PersistentFlags().BoolVar(&tui.DisableColoredOutput, "no-color", false, "Disables colored stdout|stderr output")
}

func Execute() {
rootCmd.Execute()
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}

func readLocalConfiguration() {
Expand Down Expand Up @@ -206,16 +208,6 @@ func requireProjectContext() {
configuration.GetCmdContext().Build = &config.Build
}

func getAppNameFromArtifact(artifactPath string) string {
artifactName := filepath.Base(artifactPath)
splitedArtifactName := strings.SplitN(artifactName, "-", 2)
// If artifact name doesn't contain hyphens we consider the form "appName.tar.gz", else we consider App.Name the string up to the first hyphen.
if len(splitedArtifactName) == 1 {
return strings.TrimSuffix(splitedArtifactName[0], ".tar.gz")
} else {
return splitedArtifactName[0]
}
}

func requireBuildArtifact() {
if artifact != "" {
Expand Down
4 changes: 2 additions & 2 deletions data/templates/c/.template-info.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: "c"
description: "An application template for the C language."
dependencies:
compile: [ gcc, make, g++ ]
runtime: [ json-c ]
compile: [cc, make, g++]
runtime: [json-c]
base: _base
4 changes: 2 additions & 2 deletions data/templates/cpp/.template-info.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: "cpp"
description: "An application template for the C++ language."
dependencies:
compile: [ g++, make ]
runtime: [ libstdc++, json-c ]
compile: [g++, make]
runtime: [libstdc++, json-c]
base: _base
4 changes: 2 additions & 2 deletions data/templates/go/.template-info.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: "go"
description: "An application template for the Go language."
dependencies:
compile: [ go, make ]
runtime: [ json-c ]
compile: [go, make]
runtime: [json-c]
base: _base
Loading
Loading