Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add edge commands #598

Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
465058b
add edge bootstrap command
maciaszczykm Jan 16, 2025
531a863
update args
maciaszczykm Jan 16, 2025
17cb674
update client
maciaszczykm Jan 16, 2025
9231a2d
add client method
maciaszczykm Jan 16, 2025
9b54509
fix cluster bootstrap command
maciaszczykm Jan 16, 2025
1863010
add more client methods
maciaszczykm Jan 16, 2025
066466a
draft edge boostrap logic
maciaszczykm Jan 16, 2025
a1590a8
dry
maciaszczykm Jan 16, 2025
5e7afd4
refactor
maciaszczykm Jan 16, 2025
3974081
refactor
maciaszczykm Jan 16, 2025
b457ec5
reduce poll interval
maciaszczykm Jan 16, 2025
cceeb42
add missing cluster attributes
maciaszczykm Jan 16, 2025
43d0204
reinstall agent if a handle is already taken
maciaszczykm Jan 16, 2025
beb3bcc
add logs
maciaszczykm Jan 16, 2025
608614f
add edge image command
maciaszczykm Jan 17, 2025
f940aef
fix config templating
maciaszczykm Jan 17, 2025
65488df
get build script
maciaszczykm Jan 17, 2025
b6589f6
try to run build
maciaszczykm Jan 17, 2025
f81a44a
fix permissions issue
maciaszczykm Jan 17, 2025
4f4fd0d
add default username
maciaszczykm Jan 17, 2025
ba58638
update console client
maciaszczykm Jan 17, 2025
06c27c9
update registration check
maciaszczykm Jan 17, 2025
df14248
align with the new installation script
maciaszczykm Jan 17, 2025
9d095d8
split logic
maciaszczykm Jan 17, 2025
2191c47
use exec from utils
maciaszczykm Jan 17, 2025
630bac6
add more attributes
maciaszczykm Jan 17, 2025
adfb550
work in progress
maciaszczykm Jan 17, 2025
f3b1ca8
use config file
maciaszczykm Jan 17, 2025
567d641
update .gitignore
maciaszczykm Jan 17, 2025
38ccfe4
add logs
maciaszczykm Jan 17, 2025
b2886bb
refactor
maciaszczykm Jan 17, 2025
44dd853
refactor
maciaszczykm Jan 17, 2025
20c9dd8
add more logs
maciaszczykm Jan 20, 2025
7386774
use bundle config
maciaszczykm Jan 20, 2025
6b844ce
refactor
maciaszczykm Jan 20, 2025
c566925
refactor
maciaszczykm Jan 20, 2025
3e34cd9
refactor
maciaszczykm Jan 20, 2025
0730647
ignore img files
maciaszczykm Jan 20, 2025
02f8fee
bump console client
maciaszczykm Jan 21, 2025
4076e59
align with the new client
maciaszczykm Jan 21, 2025
2a3bbcf
add wifi setup
maciaszczykm Jan 21, 2025
dac01c4
support cloud config override
maciaszczykm Jan 21, 2025
bf5f13d
add new line
maciaszczykm Jan 21, 2025
0e65a71
handle restarts
maciaszczykm Jan 21, 2025
28b0097
fix error matching
maciaszczykm Jan 21, 2025
4525b1d
update project arg docs
maciaszczykm Jan 21, 2025
48f0520
add bootstrap token mutation
maciaszczykm Jan 21, 2025
2d707e2
add user query
maciaszczykm Jan 21, 2025
3c98285
use bootstrap tokens
maciaszczykm Jan 21, 2025
0fa2e58
refactor
maciaszczykm Jan 21, 2025
c537b18
update file utils
maciaszczykm Jan 21, 2025
363a0db
fix log message
maciaszczykm Jan 21, 2025
1ce7414
improve ux
maciaszczykm Jan 21, 2025
5c127fd
improve ux
maciaszczykm Jan 21, 2025
446e63f
improve ux
maciaszczykm Jan 21, 2025
8b9114e
gen mocks
maciaszczykm Jan 21, 2025
badcd6e
load images from plural config
maciaszczykm Jan 23, 2025
35ad222
export constants
maciaszczykm Jan 23, 2025
b7284c2
refactor
maciaszczykm Jan 23, 2025
b58e5c9
update args docs
maciaszczykm Jan 24, 2025
9fbace3
Merge remote-tracking branch 'origin/main' into marcin/prod-3106-util…
maciaszczykm Jan 27, 2025
f6be74a
remove project param
maciaszczykm Jan 30, 2025
e321a50
do not reinstall agent
maciaszczykm Jan 30, 2025
7518c54
ignore system file
maciaszczykm Jan 31, 2025
c525a0d
Delete .DS_Store
maciaszczykm Jan 31, 2025
7db6df5
move log lines
maciaszczykm Jan 31, 2025
434692f
Merge remote-tracking branch 'origin/marcin/prod-3106-utilize-cluster…
maciaszczykm Jan 31, 2025
f05ce04
refactor
maciaszczykm Jan 31, 2025
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
54 changes: 1 addition & 53 deletions cmd/command/cd/cd.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,15 @@ package cd
import (
"fmt"
"os"
"strings"

"github.com/pluralsh/plural-cli/pkg/cd"
"github.com/pluralsh/plural-cli/pkg/client"
"github.com/pluralsh/plural-cli/pkg/common"
"github.com/pluralsh/plural-cli/pkg/config"
"github.com/pluralsh/plural-cli/pkg/console"
"github.com/pluralsh/plural-cli/pkg/utils"
"github.com/pluralsh/polly/algorithms"
"github.com/urfave/cli"
"helm.sh/helm/v3/pkg/action"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/yaml"
)

func init() {
Expand Down Expand Up @@ -140,7 +136,7 @@ func (p *Plural) handleInstallDeploymentsOperator(c *cli.Context) error {
// we don't care if this fails to init as this command can be auth-less
_ = p.InitConsoleClient(consoleToken, consoleURL)

return p.doInstallOperator(c.String("url"), c.String("token"), c.String("values"))
return p.DoInstallOperator(c.String("url"), c.String("token"), c.String("values"))
}

func (p *Plural) handleUninstallOperator(_ *cli.Context) error {
Expand All @@ -151,54 +147,6 @@ func (p *Plural) handleUninstallOperator(_ *cli.Context) error {
return console.UninstallAgent(console.OperatorNamespace)
}

func (p *Plural) doInstallOperator(url, token, values string) error {
err := p.InitKube()
if err != nil {
return err
}
alreadyExists, err := console.IsAlreadyAgentInstalled(p.Kube.GetClient())
if err != nil {
return err
}
if alreadyExists && !common.Confirm("the deployment operator is already installed. Do you want to replace it", "PLURAL_INSTALL_AGENT_CONFIRM_IF_EXISTS") {
utils.Success("deployment operator is already installed, skip installation\n")
return nil
}

err = p.Kube.CreateNamespace(console.OperatorNamespace, false)
if err != nil && !apierrors.IsAlreadyExists(err) {
return err
}

vals := map[string]interface{}{}
globalVals := map[string]interface{}{}
version := ""

if p.ConsoleClient != nil {
settings, err := p.ConsoleClient.GetGlobalSettings()
if err == nil && settings != nil {
version = strings.Trim(settings.AgentVsn, "v")
if settings.AgentHelmValues != nil {
if err := yaml.Unmarshal([]byte(*settings.AgentHelmValues), &globalVals); err != nil {
return err
}
}
}
}

if values != "" {
if err := utils.YamlFile(values, &vals); err != nil {
return err
}
}
vals = algorithms.Merge(vals, globalVals)
err = console.InstallAgent(url, token, console.OperatorNamespace, version, vals)
if err == nil {
utils.Success("deployment operator installed successfully\n")
}
return err
}

func confirmCluster(url, token string) (bool, error) {
consoleClient, err := console.NewConsoleClient(token, url)
if err != nil {
Expand Down
20 changes: 2 additions & 18 deletions cmd/command/cd/cd_clusters.go
Original file line number Diff line number Diff line change
Expand Up @@ -415,22 +415,6 @@ func (p *Plural) handleClusterReinstall(c *cli.Context) error {
return p.ReinstallOperator(c, id, name)
}

func (p *Plural) ReinstallOperator(c *cli.Context, id, handle *string) error {
deployToken, err := p.ConsoleClient.GetDeployToken(id, handle)
if err != nil {
return err
}

url := p.ConsoleClient.ExtUrl()
if cluster, err := p.ConsoleClient.GetCluster(id, handle); err == nil {
if agentUrl, err := p.ConsoleClient.AgentUrl(cluster.ID); err == nil {
url = agentUrl
}
}

return p.doInstallOperator(url, deployToken, c.String("values"))
}

func (p *Plural) handleClusterBootstrap(c *cli.Context) error {
if err := p.InitConsoleClient(consoleToken, consoleURL); err != nil {
return err
Expand All @@ -444,7 +428,7 @@ func (p *Plural) handleClusterBootstrap(c *cli.Context) error {
if c.String("project") != "" {
project, err := p.ConsoleClient.GetProject(c.String("project"))
if err != nil {
return nil
return err
}
if project == nil {
return fmt.Errorf("Could not find project %s", c.String("project"))
Expand Down Expand Up @@ -491,5 +475,5 @@ func (p *Plural) handleClusterBootstrap(c *cli.Context) error {

deployToken := *existing.CreateCluster.DeployToken
utils.Highlight("installing agent on %s with url %s\n", c.String("name"), p.ConsoleClient.Url())
return p.doInstallOperator(url, deployToken, c.String("values"))
return p.DoInstallOperator(url, deployToken, c.String("values"))
}
243 changes: 243 additions & 0 deletions cmd/command/edge/edge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
package edge

import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"

gqlclient "github.com/pluralsh/console/go/client"
"github.com/pluralsh/plural-cli/pkg/client"
"github.com/pluralsh/plural-cli/pkg/console/errors"
"github.com/pluralsh/plural-cli/pkg/utils"
"github.com/samber/lo"
"github.com/urfave/cli"
"helm.sh/helm/v3/pkg/action"
"k8s.io/apimachinery/pkg/util/wait"
)

var consoleToken string
var consoleURL string

type Plural struct {
client.Plural
HelmConfiguration *action.Configuration
}

func init() {
consoleToken = ""
consoleURL = ""
}

func Command(clients client.Plural, helmConfiguration *action.Configuration) cli.Command {
return cli.Command{
Name: "edge",
Usage: "manage edge clusters",
Subcommands: Commands(clients, helmConfiguration),
Flags: []cli.Flag{
cli.StringFlag{
Name: "token",
Usage: "console token",
EnvVar: "PLURAL_CONSOLE_TOKEN",
Destination: &consoleToken,
},
cli.StringFlag{
Name: "url",
Usage: "console url address",
EnvVar: "PLURAL_CONSOLE_URL",
Destination: &consoleURL,
},
},
Category: "Edge",
}
}

func Commands(clients client.Plural, helmConfiguration *action.Configuration) []cli.Command {
p := Plural{
HelmConfiguration: helmConfiguration,
Plural: clients,
}
return []cli.Command{
{
Name: "image",
Action: p.handleEdgeImage,
Usage: "prepares image ready to be used on Raspberry Pi 4",
Flags: []cli.Flag{
cli.StringFlag{
Name: "username",
Usage: "name for the initial user account",
Required: true,
},
cli.StringFlag{
Name: "password",
Usage: "password for the initial user account",
Required: true,
},
},
},
{
Name: "bootstrap",
Action: p.handleEdgeBootstrap,
Usage: "registers edge cluster and installs agent onto it using the current kubeconfig",
Flags: []cli.Flag{
cli.StringFlag{
Name: "machine-id",
Usage: "the unique id of the edge device on which this cluster runs",
Required: true,
},
cli.StringFlag{
Name: "project",
Usage: "the project this cluster will belong to",
Required: false,
},
},
},
}
}

func (p *Plural) handleEdgeImage(c *cli.Context) error {
username := c.String("username")
password := c.String("password")

cloudConfig, err := p.getCloudConfig(username, password)
if err != nil {
return err
}

fmt.Println(cloudConfig) // TODO
Fixed Show fixed Hide fixed

return nil
}

func (p *Plural) getCloudConfig(username, password string) (string, error) {
response, err := http.Get("https://raw.githubusercontent.com/pluralsh/edge/main/hack/cloud-config.yaml")
if err != nil {
return "", err
}

defer response.Body.Close()
buffer := new(bytes.Buffer)
if _, err = buffer.ReadFrom(response.Body); err != nil {
return "", err
}

template := buffer.String()
template = strings.ReplaceAll(template, "@USERNAME@", username)
template = strings.ReplaceAll(template, "@PASSWORD@", password)
template = strings.ReplaceAll(template, "@URL@", consoleURL)
template = strings.ReplaceAll(template, "@TOKEN@", consoleToken)

return template, nil
}

func (p *Plural) handleEdgeBootstrap(c *cli.Context) error {
machineID := c.String("machine-id")
project := c.String("project")

if err := p.InitConsoleClient(consoleToken, consoleURL); err != nil {
return err
}

registrationAttributes, err := p.getClusterRegistrationAttributes(machineID, project)
if err != nil {
return err
}

utils.Highlight("registering new cluster on %s machine\n", machineID)
_, err = p.ConsoleClient.CreateClusterRegistration(*registrationAttributes) // TODO: Handle the case when it already exists, i.e. after reboot.
if err != nil {
return err
}

utils.Highlight("waiting for registration to be completed\n")
var complete bool
var registration *gqlclient.ClusterRegistrationFragment
_ = wait.PollUntilContextCancel(context.Background(), 30*time.Second, true, func(_ context.Context) (done bool, err error) {
complete, registration = p.ConsoleClient.IsClusterRegistrationComplete(machineID)
return complete, nil
})

clusterAttributes, err := p.getClusterAttributes(registration)
if err != nil {
return err
}

utils.Highlight("creating %s cluster\n", registration.Name)
cluster, err := p.ConsoleClient.CreateCluster(*clusterAttributes)
if err != nil {
if errors.Like(err, "handle") {
handle := lo.ToPtr(clusterAttributes.Name)
if clusterAttributes.Handle != nil {
handle = clusterAttributes.Handle
}
return p.ReinstallOperator(c, nil, handle)
}
return err
}

if cluster.CreateCluster.DeployToken == nil {
return fmt.Errorf("could not fetch deploy token from cluster")
}

url := p.ConsoleClient.ExtUrl()
if agentUrl, err := p.ConsoleClient.AgentUrl(cluster.CreateCluster.ID); err == nil {
url = agentUrl
}

utils.Highlight("installing agent on %s cluster with %s URL\n", registration.Name, p.ConsoleClient.Url())
return p.DoInstallOperator(url, *cluster.CreateCluster.DeployToken, "")
}

func (p *Plural) getClusterRegistrationAttributes(machineID, project string) (*gqlclient.ClusterRegistrationCreateAttributes, error) {
attributes := gqlclient.ClusterRegistrationCreateAttributes{MachineID: machineID}

if project != "" {
proj, err := p.ConsoleClient.GetProject(project)
if err != nil {
return nil, err
}
if proj == nil {
return nil, fmt.Errorf("cannot find %s project", project)
}
attributes.ProjectID = lo.ToPtr(proj.ID)
}

return &attributes, nil
}

func (p *Plural) getClusterAttributes(registration *gqlclient.ClusterRegistrationFragment) (*gqlclient.ClusterAttributes, error) {
attributes := gqlclient.ClusterAttributes{
Name: registration.Name,
Handle: &registration.Handle,
}

if registration.Tags != nil {
attributes.Tags = lo.Map(registration.Tags, func(tag *gqlclient.ClusterTags, index int) *gqlclient.TagAttributes {
if tag == nil {
return nil
}

return &gqlclient.TagAttributes{
Name: tag.Name,
Value: tag.Value,
}
})
}

if registration.Metadata != nil {
metadata, err := json.Marshal(registration.Metadata)
if err != nil {
return nil, err
}
attributes.Metadata = lo.ToPtr(string(metadata))
}

if registration.Project != nil {
attributes.ProjectID = &registration.Project.ID
}

return &attributes, nil
}
2 changes: 2 additions & 0 deletions cmd/command/plural/plural.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/pluralsh/plural-cli/cmd/command/config"
cryptocmd "github.com/pluralsh/plural-cli/cmd/command/crypto"
"github.com/pluralsh/plural-cli/cmd/command/down"
"github.com/pluralsh/plural-cli/cmd/command/edge"
cmdinit "github.com/pluralsh/plural-cli/cmd/command/init"
"github.com/pluralsh/plural-cli/cmd/command/mgmt"
"github.com/pluralsh/plural-cli/cmd/command/pr"
Expand Down Expand Up @@ -108,6 +109,7 @@ func CreateNewApp(plural *Plural) *cli.App {
cryptocmd.Command(plural.Plural),
clone.Command(),
down.Command(),
edge.Command(plural.Plural, plural.HelmConfiguration),
mgmt.Command(plural.Plural),
profile.Command(),
stacks.Command(plural.Plural),
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ require (
github.com/olekukonko/tablewriter v0.0.5
github.com/packethost/packngo v0.31.0
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
github.com/pluralsh/console/go/client v1.25.4
github.com/pluralsh/console/go/client v1.26.0
github.com/pluralsh/console/go/controller v0.0.0-20250106171545-b65ee8bd2d2e
github.com/pluralsh/gqlclient v1.12.2
github.com/pluralsh/plural-operator v0.5.5
Expand Down
Loading
Loading