Extract some commands into kairosctl (#310)

This PR extracts the registration command into a `kairos-register`
binary of its own. The old sub command is kept so users can see a
deprecation notice and adapt for a future release when it's removed. The
version number is shared between binaries.

⚠️ I'm not entirely sure about the gorelease, and would benefit
from a couple of extra 👀 on this, thanks!

relates to kairos-io/kairos#1211

---------

Signed-off-by: Mauro Morales <mauro.morales@spectrocloud.com>
Signed-off-by: Mauro Morales <contact@mauromorales.com>
Signed-off-by: Itxaka <itxaka.garcia@spectrocloud.com>
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: Dimitris Karakasilis <dimitris@karakasilis.me>
Co-authored-by: Itxaka <itxaka.garcia@spectrocloud.com>
Co-authored-by: ci-robbot [bot] <105103991+ci-robbot@users.noreply.github.com>
Co-authored-by: mudler <mudler@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
This commit is contained in:
Mauro Morales 2023-04-21 11:04:15 +02:00 committed by GitHub
parent b6d6e39d23
commit d88983a906
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 351 additions and 224 deletions

View File

@ -13,18 +13,43 @@ builds:
main: ./
id: "kairos-cli"
binary: "kairos-cli"
- ldflags:
- -w -s
env:
- CGO_ENABLED=0
goos:
- linux
- windows
goarch:
- amd64
- 386
main: ./cli/kairosctl
id: "kairosctl"
binary: "kairosctl"
source:
rlcp: true
enabled: true
name_template: 'kairos-cli-{{ .Tag }}-source'
archives:
# Default template uses underscores instead of -
- name_template: "kairos-cli-{{ .Tag }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
replacements:
darwin: Darwin
linux: Linux
windows: Windows
386: i386
amd64: x86_64
- name_template: >-
kairos-cli-
{{ .Tag }}-
{{ .Os }}-
{{ .Arch }}
{{ if .Arm }}v{{ .Arm }}{{ end }}
id: kairos-id
builds:
- "kairos-id"
- name_template: >-
kairosctl-
{{ .Tag }}-
{{ .Os }}-
{{ .Arch }}
{{ if .Arm }}v{{ .Arm }}{{ end }}
id: kairosctl
builds:
- "kairosctl"
checksum:
name_template: 'kairos-cli-{{ .Tag }}-checksums.txt'
snapshot:

View File

@ -94,8 +94,13 @@ build-kairos-agent-provider:
FROM +go-deps
DO +BUILD_GOLANG --BIN=agent-provider-kairos --SRC=./ --CGO_ENABLED=$CGO_ENABLED
build-kairosctl:
FROM +go-deps
DO +BUILD_GOLANG --BIN=kairosctl --SRC=./cli/kairosctl --CGO_ENABLED=$CGO_ENABLED
build:
BUILD +build-kairos-agent-provider
BUILD +build-kairosctl
version:
FROM alpine

50
cli/kairosctl/main.go Normal file
View File

@ -0,0 +1,50 @@
package main
import (
"fmt"
"os"
"github.com/kairos-io/kairos-sdk/bus"
iCli "github.com/kairos-io/provider-kairos/internal/cli"
"github.com/kairos-io/provider-kairos/internal/provider"
"github.com/urfave/cli/v2"
)
func checkErr(err error) {
if err != nil {
fmt.Println(err)
os.Exit(1)
}
os.Exit(0)
}
func main() {
if len(os.Args) >= 2 && bus.IsEventDefined(os.Args[1]) {
checkErr(provider.Start())
}
checkErr(Start())
}
func Start() error {
toolName := "kairosctl"
name := toolName
app := &cli.App{
Name: name,
Version: iCli.VERSION,
Authors: []*cli.Author{
{
Name: iCli.Author,
},
},
Copyright: iCli.Author,
Commands: []*cli.Command{
iCli.RegisterCMD(toolName),
iCli.BridgeCMD(toolName),
&iCli.GetKubeConfigCMD,
&iCli.RoleCMD,
},
}
return app.Run(os.Args)
}

View File

@ -17,6 +17,101 @@ import (
"github.com/urfave/cli/v2"
)
func BridgeCMD(toolName string) *cli.Command {
usage := "Connect to a kairos VPN network"
description := `
Starts a bridge with a kairos network or a node.
# With a network
By default, "bridge" will create a VPN network connection to the node with the token supplied, thus it requires elevated permissions in order to work.
For example:
$ sudo %s bridge --network-token <TOKEN>
Will start a VPN, which local ip is fixed to 10.1.0.254 (tweakable with --address).
The API will also be accessible at http://127.0.0.1:8080
# With a node
"%s bridge" can be used also to connect over to a node in recovery mode. When operating in this modality kairos bridge requires no specific permissions, indeed a tunnel
will be created locally to connect to the machine remotely.
For example:
$ %s bridge --qr-code-image /path/to/image.png
Will scan the QR code in the image and connect over. Further instructions on how to connect over will be printed out to the screen.
See also: https://kairos.io/docs/reference/recovery_mode/
`
if toolName != "kairosctl" {
usage += " (WARNING: this command will be deprecated in the next release, use the kairosctl binary instead)"
description = "\t\tWARNING: This command will be deprecated in the next release. Please use the new kairosctl binary instead.\n" + description
}
return &cli.Command{
Name: "bridge",
UsageText: fmt.Sprintf("%s %s", toolName, "bridge --network-token XXX"),
Usage: usage,
Description: fmt.Sprintf(description, toolName, toolName, toolName),
Flags: []cli.Flag{
&cli.StringFlag{
Name: "network-token",
Required: false,
EnvVars: []string{"NETWORK_TOKEN"},
Usage: "Network token to connect over",
},
&cli.StringFlag{
Name: "log-level",
Required: false,
EnvVars: []string{"LOGLEVEL"},
Value: "info",
Usage: "Bridge log level",
},
&cli.BoolFlag{
Name: "qr-code-snapshot",
Required: false,
Usage: "Bool to take a local snapshot instead of reading from an image file for recovery",
EnvVars: []string{"QR_CODE_SNAPSHOT"},
},
&cli.StringFlag{
Name: "qr-code-image",
Usage: "Path to an image containing a valid QR code for recovery mode",
Required: false,
EnvVars: []string{"QR_CODE_IMAGE"},
},
&cli.StringFlag{
Name: "api",
Value: "127.0.0.1:8080",
Usage: "Listening API url",
},
&cli.BoolFlag{
Name: "dhcp",
EnvVars: []string{"DHCP"},
Usage: "Enable DHCP",
},
&cli.StringFlag{
Value: "10.1.0.254/24",
Name: "address",
EnvVars: []string{"ADDRESS"},
Usage: "Specify an address for the bridge",
},
&cli.StringFlag{
Value: "/tmp/kairos",
Name: "lease-dir",
EnvVars: []string{"lease-dir"},
Usage: "DHCP Lease directory",
},
},
Action: bridge,
}
}
// bridge is just starting a VPN with edgevpn to the given network token.
func bridge(c *cli.Context) error {
qrCodePath := ""

View File

@ -0,0 +1,37 @@
package cli
import (
"encoding/base64"
"fmt"
"strings"
edgeVPNClient "github.com/mudler/edgevpn/api/client"
"github.com/mudler/edgevpn/api/client/service"
"github.com/urfave/cli/v2"
)
var GetKubeConfigCMD = cli.Command{
Name: "get-kubeconfig",
Usage: "Return a deployment kubeconfig",
UsageText: "Retrieve a kairos network kubeconfig (only for automated deployments)",
Description: `
Retrieve a network kubeconfig and prints out to screen.
If a deployment was bootstrapped with a network token, you can use this command to retrieve the master node kubeconfig of a network id.
For example:
$ kairos get-kubeconfig --network-id kairos
`,
Flags: networkAPI,
Action: func(c *cli.Context) error {
cc := service.NewClient(
c.String("network-id"),
edgeVPNClient.NewClient(edgeVPNClient.WithHost(c.String("api"))))
str, _ := cc.Get("kubeconfig", "master")
b, _ := base64.RawURLEncoding.DecodeString(str)
masterIP, _ := cc.Get("master", "ip")
fmt.Println(strings.ReplaceAll(string(b), "127.0.0.1", masterIP))
return nil
},
}

View File

@ -3,13 +3,76 @@ package cli
import (
"context"
"fmt"
"io/ioutil" // nolint
"os"
"github.com/urfave/cli/v2"
nodepair "github.com/mudler/go-nodepair"
qr "github.com/mudler/go-nodepair/qrcode"
)
// RegisterCMD is only used temporarily to avoid duplication while the kairosctl sub-command is deprecated.
func RegisterCMD(toolName string) *cli.Command {
subCommandName := "register"
fullName := fmt.Sprintf("%s %s", toolName, subCommandName)
usage := "Registers and bootstraps a node"
description := fmt.Sprintf(`
Bootstraps a node which is started in pairing mode. It can send over a configuration file used to install the kairos node.
For example:
$ %s --config config.yaml --device /dev/sda ~/Downloads/screenshot.png
will decode the QR code from ~/Downloads/screenshot.png and bootstrap the node remotely.
If the image is omitted, a screenshot will be taken and used to decode the QR code.
See also https://kairos.io/docs/getting-started/ for documentation.
`, fullName)
if toolName != "kairosctl" {
usage += " (WARNING: this command will be deprecated in the next release, use the kairosctl binary instead)"
description = "\t\tWARNING: This command will be deprecated in the next release. Please use the new kairosctl binary to register your nodes.\n" + description
}
return &cli.Command{
Name: subCommandName,
UsageText: fmt.Sprintf("%s --reboot --device /dev/sda /image/snapshot.png", fullName),
Usage: usage,
Description: description,
ArgsUsage: "Register optionally accepts an image. If nothing is passed will take a screenshot of the screen and try to decode the QR code",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "config",
Usage: "Kairos YAML configuration file",
Required: true,
},
&cli.StringFlag{
Name: "device",
Usage: "Device used for the installation target",
},
&cli.BoolFlag{
Name: "reboot",
Usage: "Reboot node after installation",
},
&cli.BoolFlag{
Name: "poweroff",
Usage: "Shutdown node after installation",
},
&cli.StringFlag{
Name: "log-level",
Usage: "Set log level",
},
},
Action: func(c *cli.Context) error {
var ref string
if c.Args().Len() == 1 {
ref = c.Args().First()
}
return register(c.String("log-level"), ref, c.String("config"), c.String("device"), c.Bool("reboot"), c.Bool("poweroff"))
},
}
}
// isDirectory determines if a file represented
// by `path` is a directory or not.
func isDirectory(path string) (bool, error) {
@ -33,7 +96,7 @@ func isReadable(fileName string) bool {
}
func register(loglevel, arg, configFile, device string, reboot, poweroff bool) error {
b, _ := ioutil.ReadFile(configFile)
b, _ := os.ReadFile(configFile)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

55
internal/cli/role.go Normal file
View File

@ -0,0 +1,55 @@
package cli
import (
"fmt"
edgeVPNClient "github.com/mudler/edgevpn/api/client"
"github.com/mudler/edgevpn/api/client/service"
"github.com/urfave/cli/v2"
)
var RoleCMD = cli.Command{
Name: "role",
Usage: "Set or list node roles",
Subcommands: []*cli.Command{
{
Flags: networkAPI,
Name: "set",
Usage: "Set a node role",
UsageText: "kairos role set <UUID> master",
Description: `
Sets a node role propagating the setting to the network.
A role must be set prior to the node joining a network. You can retrieve a node UUID by running "kairos uuid".
Example:
$ (node A) kairos uuid
$ (node B) kairos role set <UUID of node A> master
`,
Action: func(c *cli.Context) error {
cc := service.NewClient(
c.String("network-id"),
edgeVPNClient.NewClient(edgeVPNClient.WithHost(c.String("api"))))
return cc.Set("role", c.Args().Get(0), c.Args().Get(1))
},
},
{
Flags: networkAPI,
Name: "list",
Description: "List node roles",
Action: func(c *cli.Context) error {
cc := service.NewClient(
c.String("network-id"),
edgeVPNClient.NewClient(edgeVPNClient.WithHost(c.String("api"))))
advertizing, _ := cc.AdvertizingNodes()
fmt.Println("Node\tRole")
for _, a := range advertizing {
role, _ := cc.Get("role", a)
fmt.Printf("%s\t%s\n", a, role)
}
return nil
},
},
},
}

View File

@ -1,19 +1,14 @@
package cli
import (
"encoding/base64"
"fmt"
"os"
"strconv"
"strings"
edgeVPNClient "github.com/mudler/edgevpn/api/client"
providerConfig "github.com/kairos-io/provider-kairos/internal/provider/config"
"github.com/urfave/cli/v2"
"gopkg.in/yaml.v1"
"github.com/mudler/edgevpn/api/client/service"
"github.com/mudler/edgevpn/pkg/node"
)
@ -21,6 +16,7 @@ import (
// -X 'github.com/kairos-io/provider-kairos/internal/cli.VERSION=$VERSION'
// see Earthlfile.
var VERSION = "0.0.0"
var Author = "Ettore Di Giacinto"
var networkAPI = []cli.Flag{
&cli.StringFlag{
@ -38,18 +34,19 @@ var networkAPI = []cli.Flag{
const recoveryAddr = "127.0.0.1:2222"
func Start() error {
toolName := "kairos"
app := &cli.App{
Name: "kairos-agent-provider",
Name: toolName,
Version: VERSION,
Authors: []*cli.Author{
{
Name: "Ettore Di Giacinto",
Name: Author,
},
},
Usage: "kairos CLI to bootstrap, upgrade, connect and manage a kairos network",
Description: `
The kairos CLI can be used to manage a kairos box and perform all day-two tasks, like:
- register a node
- register a node (WARNING: this command will be deprecated in the next release, use the kairosctl binary instead)
- connect to a node in recovery mode
- to establish a VPN connection
- set, list roles
@ -57,10 +54,10 @@ The kairos CLI can be used to manage a kairos box and perform all day-two tasks,
and much more.
For all the example cases, see: https://docs.kairos.io .
For all the example cases, see: https://kairos.io/docs/
`,
UsageText: ``,
Copyright: "Ettore Di Giacinto",
Copyright: Author,
Commands: []*cli.Command{
{
Name: "recovery-ssh-server",
@ -93,210 +90,10 @@ For all the example cases, see: https://docs.kairos.io .
return StartRecoveryService(c.String("token"), c.String("service"), c.String("password"), c.String("listen"))
},
},
{
Name: "register",
UsageText: "register --reboot --device /dev/sda /image/snapshot.png",
Usage: "Registers and bootstraps a node",
Description: `
Bootstraps a node which is started in pairing mode. It can send over a configuration file used to install the kairos node.
For example:
$ kairos register --config config.yaml --device /dev/sda ~/Downloads/screenshot.png
will decode the QR code from ~/Downloads/screenshot.png and bootstrap the node remotely.
If the image is omitted, a screenshot will be taken and used to decode the QR code.
See also https://kairos.io/docs/getting-started/ for documentation.
`,
ArgsUsage: "Register optionally accepts an image. If nothing is passed will take a screenshot of the screen and try to decode the QR code",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "config",
Usage: "Kairos YAML configuration file",
Required: true,
},
&cli.StringFlag{
Name: "device",
Usage: "Device used for the installation target",
},
&cli.BoolFlag{
Name: "reboot",
Usage: "Reboot node after installation",
},
&cli.BoolFlag{
Name: "poweroff",
Usage: "Shutdown node after installation",
},
&cli.StringFlag{
Name: "log-level",
Usage: "Set log level",
},
},
Action: func(c *cli.Context) error {
var ref string
if c.Args().Len() == 1 {
ref = c.Args().First()
}
return register(c.String("log-level"), ref, c.String("config"), c.String("device"), c.Bool("reboot"), c.Bool("poweroff"))
},
},
{
Name: "bridge",
UsageText: "bridge --network-token XXX",
Usage: "Connect to a kairos VPN network",
Description: `
Starts a bridge with a kairos network or a node.
# With a network
By default, "bridge" will create a VPN network connection to the node with the token supplied, thus it requires elevated permissions in order to work.
For example:
$ sudo kairos bridge --network-token <TOKEN>
Will start a VPN, which local ip is fixed to 10.1.0.254 (tweakable with --address).
The API will also be accessible at http://127.0.0.1:8080
# With a node
"kairos bridge" can be used also to connect over to a node in recovery mode. When operating in this modality kairos bridge requires no specific permissions, indeed a tunnel
will be created locally to connect to the machine remotely.
For example:
$ kairos bridge --qr-code-image /path/to/image.png
Will scan the QR code in the image and connect over. Further instructions on how to connect over will be printed out to the screen.
See also: https://docs.kairos.io/after_install/troubleshooting/#connect-to-the-cluster-network and https://docs.kairos.io/after_install/recovery_mode/
`,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "network-token",
Required: false,
EnvVars: []string{"NETWORK_TOKEN"},
Usage: "Network token to connect over",
},
&cli.StringFlag{
Name: "log-level",
Required: false,
EnvVars: []string{"LOGLEVEL"},
Value: "info",
Usage: "Bridge log level",
},
&cli.BoolFlag{
Name: "qr-code-snapshot",
Required: false,
Usage: "Bool to take a local snapshot instead of reading from an image file for recovery",
EnvVars: []string{"QR_CODE_SNAPSHOT"},
},
&cli.StringFlag{
Name: "qr-code-image",
Usage: "Path to an image containing a valid QR code for recovery mode",
Required: false,
EnvVars: []string{"QR_CODE_IMAGE"},
},
&cli.StringFlag{
Name: "api",
Value: "127.0.0.1:8080",
Usage: "Listening API url",
},
&cli.BoolFlag{
Name: "dhcp",
EnvVars: []string{"DHCP"},
Usage: "Enable DHCP",
},
&cli.StringFlag{
Value: "10.1.0.254/24",
Name: "address",
EnvVars: []string{"ADDRESS"},
Usage: "Specify an address for the bridge",
},
&cli.StringFlag{
Value: "/tmp/kairos",
Name: "lease-dir",
EnvVars: []string{"lease-dir"},
Usage: "DHCP Lease directory",
},
},
Action: bridge,
},
{
Name: "get-kubeconfig",
Usage: "Return a deployment kubeconfig",
UsageText: "Retrieve a kairos network kubeconfig (only for automated deployments)",
Description: `
Retrieve a network kubeconfig and prints out to screen.
If a deployment was bootstrapped with a network token, you can use this command to retrieve the master node kubeconfig of a network id.
For example:
$ kairos get-kubeconfig --network-id kairos
`,
Flags: networkAPI,
Action: func(c *cli.Context) error {
cc := service.NewClient(
c.String("network-id"),
edgeVPNClient.NewClient(edgeVPNClient.WithHost(c.String("api"))))
str, _ := cc.Get("kubeconfig", "master")
b, _ := base64.RawURLEncoding.DecodeString(str)
masterIP, _ := cc.Get("master", "ip")
fmt.Println(strings.ReplaceAll(string(b), "127.0.0.1", masterIP))
return nil
},
},
{
Name: "role",
Usage: "Set or list node roles",
Subcommands: []*cli.Command{
{
Flags: networkAPI,
Name: "set",
Usage: "Set a node role",
UsageText: "kairos role set <UUID> master",
Description: `
Sets a node role propagating the setting to the network.
A role must be set prior to the node joining a network. You can retrieve a node UUID by running "kairos uuid".
Example:
$ (node A) kairos uuid
$ (node B) kairos role set <UUID of node A> master
`,
Action: func(c *cli.Context) error {
cc := service.NewClient(
c.String("network-id"),
edgeVPNClient.NewClient(edgeVPNClient.WithHost(c.String("api"))))
return cc.Set("role", c.Args().Get(0), c.Args().Get(1))
},
},
{
Flags: networkAPI,
Name: "list",
Description: "List node roles",
Action: func(c *cli.Context) error {
cc := service.NewClient(
c.String("network-id"),
edgeVPNClient.NewClient(edgeVPNClient.WithHost(c.String("api"))))
advertizing, _ := cc.AdvertizingNodes()
fmt.Println("Node\tRole")
for _, a := range advertizing {
role, _ := cc.Get("role", a)
fmt.Printf("%s\t%s\n", a, role)
}
return nil
},
},
},
},
RegisterCMD(toolName),
BridgeCMD(toolName),
&GetKubeConfigCMD,
&RoleCMD,
{
Name: "create-config",
Aliases: []string{"c"},

View File

@ -23,7 +23,7 @@ var _ = Describe("kairos qr code register", Label("qrcode-register"), func() {
return err
}
out, err = utils.SH(fmt.Sprintf("kairos register --device /dev/sda --config %s %s", os.Getenv("CLOUD_INIT"), "screenshot.png"))
out, err = utils.SH(fmt.Sprintf("kairosctl register--device /dev/sda --config %s %s", os.Getenv("CLOUD_INIT"), "screenshot.png"))
fmt.Println(out)
return err