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

SSH options #140

Merged
merged 7 commits into from
May 5, 2023
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
6 changes: 6 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ jobs:
with:
snapcraft_token: ${{ secrets.SNAPCRAFT_TOKEN }}

-
name: Find version
id: version
run: echo "CLI_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV

-
name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
Expand All @@ -35,3 +40,4 @@ jobs:
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CLI_VERSION: ${{ env.MODULE_VERSION }}
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
# 0.0.101 (Unreleased)
# 0.0.102 (May 04, 2023)
* Fixed `nullstone --version` reporting the correct version instead of `dev`.
* Added support for `--container` when using `nullstone ssh|exec` commands for an ECS/Fargate app.

# 0.0.101 (Apr 28, 2023)
* Added `domain_fqdn` local when generating a domain module.
* Renamed `domain_name` local to `domain_dns_name` when generating a domain module.

Expand Down
11 changes: 9 additions & 2 deletions admin/remoter.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,18 @@ import (
"gopkg.in/nullstone-io/nullstone.v0/config"
)

type RemoteOptions struct {
Task string
Replica string
Container string
PortForwards []config.PortForward
}

type Remoter interface {
// Exec allows a user to execute a command (usually tunneling) into a running service
// This only makes sense for container-based providers
Exec(ctx context.Context, task string, cmd string) error
Exec(ctx context.Context, options RemoteOptions, cmd string) error

// Ssh allows a user to SSH into a running service
Ssh(ctx context.Context, task string, forwards []config.PortForward) error
Ssh(ctx context.Context, options RemoteOptions) error
}
80 changes: 33 additions & 47 deletions app/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,60 +8,46 @@ import (
"sort"
)

var (
version = "dev"
commit = "none"
date = "unknown"
builtBy = "unknown"
)

func Build() *cli.App {
appProviders := allApp.Providers
adminProviders := allAdmin.Providers

cliApp := &cli.App{
Version: version,
EnableBashCompletion: true,
Metadata: map[string]interface{}{
"commit": commit,
"date": date,
"builtBy": builtBy,
},
Flags: []cli.Flag{
cmd.ProfileFlag,
cmd.OrgFlag,
},
Commands: []*cli.Command{
{
Name: "version",
Action: func(c *cli.Context) error {
cli.ShowVersion(c)
return nil
},
cliApp := cli.NewApp()
cliApp.EnableBashCompletion = true
cliApp.Flags = []cli.Flag{
cmd.ProfileFlag,
cmd.OrgFlag,
}
sort.Sort(cli.FlagsByName(cliApp.Flags))
cliApp.Commands = []*cli.Command{
{
Name: "version",
Action: func(c *cli.Context) error {
cli.ShowVersion(c)
return nil
},
cmd.Configure,
cmd.SetOrg,
cmd.Stacks,
cmd.Envs,
cmd.Apps,
cmd.Blocks,
cmd.Modules,
cmd.Workspaces,
cmd.Up(),
cmd.Plan(),
cmd.Apply(),
cmd.Outputs(),
cmd.Push(appProviders),
cmd.Deploy(appProviders),
cmd.Launch(appProviders),
cmd.Logs(adminProviders),
cmd.Status(adminProviders),
cmd.Exec(adminProviders),
cmd.Ssh(adminProviders),
cmd.Profile,
},
cmd.Configure,
cmd.SetOrg,
cmd.Stacks,
cmd.Envs,
cmd.Apps,
cmd.Blocks,
cmd.Modules,
cmd.Workspaces,
cmd.Up(),
cmd.Plan(),
cmd.Apply(),
cmd.Outputs(),
cmd.Push(appProviders),
cmd.Deploy(appProviders),
cmd.Launch(appProviders),
cmd.Logs(adminProviders),
cmd.Status(adminProviders),
cmd.Exec(adminProviders),
cmd.Ssh(adminProviders),
cmd.Profile,
}
sort.Sort(cli.FlagsByName(cliApp.Flags))
sort.Sort(cli.CommandsByName(cliApp.Commands))

return cliApp
Expand Down
7 changes: 3 additions & 4 deletions aws/ec2/remoter.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"gopkg.in/nullstone-io/go-api-client.v0"
"gopkg.in/nullstone-io/nullstone.v0/admin"
"gopkg.in/nullstone-io/nullstone.v0/aws/ssm"
"gopkg.in/nullstone-io/nullstone.v0/config"
)

func NewRemoter(osWriters logging.OsWriters, nsConfig api.Config, appDetails app.Details) (admin.Remoter, error) {
Expand All @@ -30,12 +29,12 @@ type Remoter struct {
Infra Outputs
}

func (r Remoter) Exec(ctx context.Context, task string, cmd string) error {
func (r Remoter) Exec(ctx context.Context, options admin.RemoteOptions, cmd string) error {
return ExecCommand(ctx, r.Infra, cmd, nil)
}

func (r Remoter) Ssh(ctx context.Context, task string, forwards []config.PortForward) error {
parameters, err := ssm.SessionParametersFromPortForwards(forwards)
func (r Remoter) Ssh(ctx context.Context, options admin.RemoteOptions) error {
parameters, err := ssm.SessionParametersFromPortForwards(options.PortForwards)
if err != nil {
return err
}
Expand Down
6 changes: 4 additions & 2 deletions aws/ecs/exec_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import (
"gopkg.in/nullstone-io/nullstone.v0/aws/ssm"
)

func ExecCommand(ctx context.Context, infra Outputs, taskId string, cmd string, parameters map[string][]string) error {
func ExecCommand(ctx context.Context, infra Outputs, taskId string, containerName string, cmd string, parameters map[string][]string) error {
region := infra.Region
cluster := infra.ClusterArn()
containerName := infra.MainContainerName
if containerName == "" {
containerName = infra.MainContainerName
}
awsConfig := nsaws.NewConfig(infra.Deployer, region)

return ssm.StartEcsSession(ctx, awsConfig, region, cluster, taskId, containerName, cmd, parameters)
Expand Down
13 changes: 7 additions & 6 deletions aws/ecs/remoter.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"github.com/nullstone-io/deployment-sdk/outputs"
"gopkg.in/nullstone-io/go-api-client.v0"
"gopkg.in/nullstone-io/nullstone.v0/admin"
"gopkg.in/nullstone-io/nullstone.v0/config"
)

func NewRemoter(osWriters logging.OsWriters, nsConfig api.Config, appDetails app.Details) (admin.Remoter, error) {
Expand All @@ -30,7 +29,8 @@ type Remoter struct {
Infra Outputs
}

func (r Remoter) Exec(ctx context.Context, task string, cmd string) error {
func (r Remoter) Exec(ctx context.Context, options admin.RemoteOptions, cmd string) error {
task := options.Task
if task == "" {
var err error
if task, err = GetRandomTask(ctx, r.Infra); err != nil {
Expand All @@ -40,10 +40,11 @@ func (r Remoter) Exec(ctx context.Context, task string, cmd string) error {
}
}

return ExecCommand(ctx, r.Infra, task, cmd, nil)
return ExecCommand(ctx, r.Infra, task, options.Container, cmd, nil)
}

func (r Remoter) Ssh(ctx context.Context, task string, forwards []config.PortForward) error {
func (r Remoter) Ssh(ctx context.Context, options admin.RemoteOptions) error {
task := options.Task
if task == "" {
var err error
if task, err = GetRandomTask(ctx, r.Infra); err != nil {
Expand All @@ -53,9 +54,9 @@ func (r Remoter) Ssh(ctx context.Context, task string, forwards []config.PortFor
}
}

if len(forwards) > 0 {
if len(options.PortForwards) > 0 {
return fmt.Errorf("ecs provider does not support port forwarding")
}

return ExecCommand(ctx, r.Infra, task, "/bin/sh", nil)
return ExecCommand(ctx, r.Infra, task, options.Container, "/bin/sh", nil)
}
2 changes: 1 addition & 1 deletion cmd/envs.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ var EnvsDelete = &cli.Command{
Name: "delete",
Description: "Deletes the given environment. Before issuing this command, make sure you have destroyed all infrastructure in the environment. If you are deleting a preview environment, you can use the `--force` flag to skip the confirmation prompt.",
Usage: "Create new environment",
UsageText: "nullstone envs delete --stack=<stack> --env=<env> [--force]",
UsageText: "nullstone envs delete --stack=<stack> --env=<env> [--force]",
Flags: []cli.Flag{
StackRequiredFlag,
EnvFlag,
Expand Down
10 changes: 8 additions & 2 deletions cmd/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ var Exec = func(providers admin.Providers) *cli.Command {
AppFlag,
EnvFlag,
TaskFlag,
ReplicaFlag,
ContainerFlag,
},
Action: func(c *cli.Context) error {
task := c.String("task")
cmd := "/bin/sh"
if c.Args().Len() >= 1 {
cmd = c.Args().Get(c.Args().Len() - 1)
Expand All @@ -33,7 +34,12 @@ var Exec = func(providers admin.Providers) *cli.Command {
if err != nil {
return err
}
return remoter.Exec(ctx, task, cmd)
options := admin.RemoteOptions{
Task: c.String("task"),
Replica: c.String("replica"),
Container: c.String("container"),
}
return remoter.Exec(ctx, options, cmd)
})
},
}
Expand Down
11 changes: 7 additions & 4 deletions cmd/flag_org.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@ var (
)

// OrgFlag defines a flag that the CLI uses
// to contextualize API calls by that organization within Nullstone
//
// to contextualize API calls by that organization within Nullstone
//
// The organization takes the following precedence:
// `--org` flag
// `NULLSTONE_ORG` env var
// `~/.nullstone/<profile>/org` file
//
// `--org` flag
// `NULLSTONE_ORG` env var
// `~/.nullstone/<profile>/org` file
var OrgFlag = &cli.StringFlag{
Name: "org",
EnvVars: []string{"NULLSTONE_ORG"},
Expand Down
22 changes: 22 additions & 0 deletions cmd/flag_ssh.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package cmd

import "github.com/urfave/cli/v2"

var TaskFlag = &cli.StringFlag{
Name: "task",
Usage: `Select a specific task to execute the command against.
This is optional and by default will connect to a random task.
This is only used by ECS and determines which task to connect.`,
}
var ReplicaFlag = &cli.StringFlag{
Name: "replica",
Usage: `Select a specific replica to execute the command against.
This is optional and by default will connect to a random replica.
This is only used by Kubernetes clusters and determines which replica of the pod to connect.`,
}

var ContainerFlag = &cli.StringFlag{
Name: "container",
Usage: `Select a specific container within a task or pod.
If using sidecars, this allows you to connect to other containers besides the primary application container.`,
}
11 changes: 0 additions & 11 deletions cmd/flag_task.go

This file was deleted.

12 changes: 9 additions & 3 deletions cmd/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ var Ssh = func(providers admin.Providers) *cli.Command {
AppFlag,
EnvFlag,
TaskFlag,
ReplicaFlag,
ContainerFlag,
&cli.StringSliceFlag{
Name: "forward",
Aliases: []string{"L"},
Usage: "Use this to forward ports from host to local machine. Format: <local-port>:[<remote-host>]:<remote-port>",
},
},
Action: func(c *cli.Context) error {
task := c.String("task")

forwards := make([]config.PortForward, 0)
for _, arg := range c.StringSlice("forward") {
pf, err := config.ParsePortForward(arg)
Expand All @@ -51,7 +51,13 @@ var Ssh = func(providers admin.Providers) *cli.Command {
return fmt.Errorf("The Nullstone CLI does not currently support the ssh command for the %q application. (Module = %s/%s, App Category = app/%s, Platform = %s)",
appDetails.App.Name, module.OrgName, module.Name, module.Subcategory, platform)
}
return remoter.Ssh(ctx, task, forwards)
options := admin.RemoteOptions{
Task: c.String("task"),
Replica: c.String("replica"),
Container: c.String("container"),
PortForwards: forwards,
}
return remoter.Ssh(ctx, options)
})
},
}
Expand Down
3 changes: 2 additions & 1 deletion cmd/table_buffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import (
// TableBuffer builds a table of data to display on the terminal
// The TableBuffer guarantees safe merging of rows with potentially different field names
// Example: If a user is migrating an app from container to serverless,
// it's possible that the infrastructure has not fully propagated
//
// it's possible that the infrastructure has not fully propagated
type TableBuffer struct {
Fields []string
HasField map[string]bool
Expand Down
13 changes: 13 additions & 0 deletions nullstone/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,21 @@ import (
"os"
)

var (
version = "dev"
commit = "none"
date = "unknown"
builtBy = "unknown"
)

func main() {
cliApp := app.Build()
cliApp.Version = version
cliApp.Metadata = map[string]interface{}{
"commit": commit,
"date": date,
"builtBy": builtBy,
}

err := cliApp.Run(os.Args)
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions workspaces/run_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (

// GetRunConfig loads the effective run config for a workspace
// This does the following:
// 1. Pull the latest run config for the workspace
// 2. Scan module in local file system for `ns_connection` that have not been added to run config
// 1. Pull the latest run config for the workspace
// 2. Scan module in local file system for `ns_connection` that have not been added to run config
func GetRunConfig(cfg api.Config, workspace Manifest) (types.RunConfig, error) {
client := api.Client{Config: cfg}
uid, _ := uuid.Parse(workspace.WorkspaceUid)
Expand Down