Skip to content

feat(ec): update node join instructions for improved join experience #5304

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

Merged
Merged
Show file tree
Hide file tree
Changes from 2 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
23 changes: 16 additions & 7 deletions pkg/embeddedcluster/node_join.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/base64"
"fmt"
"net"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -208,29 +209,37 @@ func findInternalIPAddress(addresses []corev1.NodeAddress) *corev1.NodeAddress {
return nil
}

// GenerateAddNodeCommand returns the command a user should run to add a node with the provided token
// the command will be of the form 'embeddedcluster node join ip:port UUID'
func GenerateAddNodeCommand(ctx context.Context, kbClient kbclient.Client, token string, isAirgap bool) (string, error) {
// GenerateAddNodeCommand returns a list of commands a user should run to add a node with the
// provided token.
func GenerateAddNodeCommand(ctx context.Context, kbClient kbclient.Client, token string) ([]string, error) {
installation, err := GetCurrentInstallation(ctx, kbClient)
if err != nil {
return "", fmt.Errorf("failed to get current installation: %w", err)
return nil, fmt.Errorf("failed to get current installation: %w", err)
}

binaryName := installation.Spec.BinaryName

// get the IP of a controller node
nodeIP, err := getControllerNodeIP(ctx, kbClient)
if err != nil {
return "", fmt.Errorf("failed to get controller node IP: %w", err)
return nil, fmt.Errorf("failed to get controller node IP: %w", err)
}

// get the port of the 'admin-console' service
port, err := getAdminConsolePort(ctx, kbClient)
if err != nil {
return "", fmt.Errorf("failed to get admin console port: %w", err)
return nil, fmt.Errorf("failed to get admin console port: %w", err)
}

return fmt.Sprintf("sudo ./%s join %s:%d %s", binaryName, nodeIP, port, token), nil
address := net.JoinHostPort(nodeIP, fmt.Sprintf("%d", port))

commands := []string{
fmt.Sprintf("curl -k https://%s/api/v1/embedded-cluster/binary -o %s.tar.gz", address, binaryName),
fmt.Sprintf("tar -xvf %s.tar.gz", binaryName),
fmt.Sprintf("sudo ./%s join %s %s", binaryName, address, token),
}

return commands, nil
}

// GenerateK0sJoinCommand returns the k0s node join command, without the token but with all other required flags
Expand Down
19 changes: 6 additions & 13 deletions pkg/embeddedcluster/node_join_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,25 +80,18 @@ func TestGenerateAddNodeCommand(t *testing.T) {

req := require.New(t)

// Generate the add node command for online
gotCommand, err := GenerateAddNodeCommand(context.Background(), kbClient, "token", false)
gotCommand, err := GenerateAddNodeCommand(context.Background(), kbClient, "token")
if err != nil {
t.Fatalf("Failed to generate add node command: %v", err)
}

// Verify the generated command
wantCommand := "sudo ./my-app join 192.168.0.100:30000 token"
req.Equal(wantCommand, gotCommand)

// Generate the add node command for airgap
gotCommand, err = GenerateAddNodeCommand(context.Background(), kbClient, "token", true)
if err != nil {
t.Fatalf("Failed to generate add node command: %v", err)
wantCommands := []string{
"curl -k https://192.168.0.100:30000/api/v1/embedded-cluster/binary -o my-app.tar.gz",
"tar -xvf my-app.tar.gz",
"sudo ./my-app join 192.168.0.100:30000 token",
}

// Verify the generated command
wantCommand = "sudo ./my-app join 192.168.0.100:30000 token"
req.Equal(wantCommand, gotCommand)
req.Equal(wantCommands, gotCommand)
}

func TestGetAllNodeIPAddresses(t *testing.T) {
Expand Down
19 changes: 3 additions & 16 deletions pkg/handlers/embedded_cluster_node_join_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"github.com/google/uuid"
"github.com/replicatedhq/embedded-cluster/kinds/types/join"

"github.com/replicatedhq/kots/pkg/api/handlers/types"
"github.com/replicatedhq/kots/pkg/api/handlers/types"
"github.com/replicatedhq/kots/pkg/embeddedcluster"
"github.com/replicatedhq/kots/pkg/k8sutil"
"github.com/replicatedhq/kots/pkg/kotsutil"
Expand Down Expand Up @@ -38,35 +38,22 @@ func (h *Handler) GenerateEmbeddedClusterNodeJoinCommand(w http.ResponseWriter,
return
}

apps, err := store.GetStore().ListInstalledApps()
if err != nil {
logger.Error(fmt.Errorf("failed to list installed apps: %w", err))
w.WriteHeader(http.StatusInternalServerError)
return
}
if len(apps) == 0 {
logger.Error(fmt.Errorf("no installed apps found"))
w.WriteHeader(http.StatusInternalServerError)
return
}
app := apps[0]

kbClient, err := h.GetKubeClient(r.Context())
if err != nil {
logger.Error(fmt.Errorf("failed to get kubeclient: %w", err))
w.WriteHeader(http.StatusInternalServerError)
return
}

nodeJoinCommand, err := embeddedcluster.GenerateAddNodeCommand(r.Context(), kbClient, token, app.IsAirgap)
nodeJoinCommands, err := embeddedcluster.GenerateAddNodeCommand(r.Context(), kbClient, token)
if err != nil {
logger.Error(fmt.Errorf("failed to generate add node command: %w", err))
w.WriteHeader(http.StatusInternalServerError)
return
}

JSON(w, http.StatusOK, types.GenerateEmbeddedClusterNodeJoinCommandResponse{
Command: []string{nodeJoinCommand},
Command: nodeJoinCommands,
})
}

Expand Down
42 changes: 28 additions & 14 deletions web/src/components/apps/EmbeddedClusterManagement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ const EmbeddedClusterManagement = ({
});

type AddNodeCommandResponse = {
command: string;
command: string[];
expiry: string;
};

Expand Down Expand Up @@ -431,13 +431,19 @@ const EmbeddedClusterManagement = ({
{rolesData?.roles &&
rolesData.roles.length > 1 &&
"Select one or more roles to assign to the new node."}{" "}
Copy the join command and run it on the machine you'd like to join to
Copy the join commands and run them on the machine you'd like to join to
the cluster.
</p>
</div>
);
};

const addNodesCommandInstructions = [
"Download the installation assets",
"Extract the installation assets",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"Download the installation assets",
"Extract the installation assets",
"Download the binary on the new node",
"Extract the binary",

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

"Join the node to the cluster",
];

const AddNodeCommands = () => {
return (
<>
Expand Down Expand Up @@ -506,18 +512,26 @@ const EmbeddedClusterManagement = ({
</p>
)}
{!generateAddNodeCommandLoading && generateAddNodeCommand?.command && (
<>
<CodeSnippet
key={selectedNodeTypes.toString()}
language="bash"
canCopy={true}
onCopyText={
<span className="u-textColor--success">Copied!</span>
}
>
{generateAddNodeCommand?.command}
</CodeSnippet>
</>
<div className="tw-flex tw-flex-col tw-gap-4 tw-mt-4">
{generateAddNodeCommand.command.map((command, index) => (
<div key={command}>
{addNodesCommandInstructions[index] && (
<p className="tw-text-gray-600 tw-font-semibold">
{addNodesCommandInstructions[index]}
</p>
)}
<CodeSnippet
language="bash"
canCopy={true}
onCopyText={
<span className="u-textColor--success">Copied!</span>
}
>
{command}
</CodeSnippet>
</div>
))}
</div>
)}
</div>
</>
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/modals/AddANodeModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const AddANodeModal = ({
{rolesData?.roles &&
rolesData.roles.length > 1 &&
"Select one or more roles to assign to the new node. "}
Copy the join command and run it on the machine you'd like to join to
Copy the join commands and run them on the machine you'd like to join to
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just because they're not all join commands

Suggested change
Copy the join commands and run them on the machine you'd like to join to
Copy the commands and run them on the machine you'd like to join to

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

the cluster.
</p>
{children}
Expand Down
Loading