Documentation ¶
Index ¶
- Variables
- func AppWorkspaceAction(c *cli.Context, fn AppWorkspaceFn) error
- func BlockWorkspaceAction(c *cli.Context, fn BlockWorkspaceActionFn) error
- func CancellableAction(fn func(ctx context.Context) error) error
- func CreateDeploy(nsConfig api.Config, appDetails app.Details, version string) (*types.Deploy, error)
- func DetectAppVersion(c *cli.Context) string
- func FindAppDetails(cfg api.Config, appName, stackName, envName string) (app.Details, error)
- func GetApp(c *cli.Context) string
- func GetEnvironment(c *cli.Context) string
- func GetOrg(c *cli.Context, profile config.Profile) string
- func GetProfile(c *cli.Context) string
- func ParseAppEnv(c *cli.Context, isEnvRequired bool, fn ParseAppEnvFn) error
- func ProfileAction(c *cli.Context, fn ProfileFn) error
- func SetupProfileCmd(c *cli.Context) (*config.Profile, api.Config, error)
- func WatchAction(ctx context.Context, watchInterval time.Duration, fn func(w io.Writer) error) error
- type AppWorkspaceFn
- type AppWorkspaceInfo
- type BlockWorkspaceActionFn
- type NsStatus
- type ParseAppEnvFn
- type ProfileFn
- type TableBuffer
Constants ¶
This section is empty.
Variables ¶
View Source
var ( AddressFlag = &cli.StringFlag{ Name: "address", Value: api.DefaultAddress, Usage: "Nullstone API Address", } ApiKeyFlag = &cli.StringFlag{ Name: "api-key", Value: "", Usage: "Nullstone API Key", } )
View Source
var AppFlag = &cli.StringFlag{ Name: "app", Usage: "Set the application name.", EnvVars: []string{"NULLSTONE_APP"}, }
View Source
var AppSourceFlag = &cli.StringFlag{ Name: "source", Usage: `The source artifact to push. app/container: This is the docker image to push. This follows the same syntax as 'docker push NAME[:TAG]'. app/serverless: This is a .zip archive to push.`, Required: true, }
View Source
var AppVersionFlag = &cli.StringFlag{
Name: "version",
Usage: `Push/Deploy the artifact with this version.
If not specified, will retrieve short sha from your latest commit.
app/container: If specified, will push the docker image with version as the image tag. Otherwise, uses source tag.
app/serverless: This is required to upload the artifact.`,
}
View Source
var Apply = func() *cli.Command { return &cli.Command{ Name: "apply", Usage: "Runs an apply with optional auto-approval", UsageText: "nullstone apply [--stack=<stack-name>] --block=<block-name> --env=<env-name> [options]", Flags: []cli.Flag{ StackFlag, BlockFlag, EnvFlag, &cli.BoolFlag{ Name: "wait", Aliases: []string{"w"}, Usage: "Stream the Terraform logs while waiting for Nullstone to run the apply.", }, &cli.BoolFlag{ Name: "auto-approve", Usage: "Auto-approve any changes made in Terraform", }, &cli.StringSliceFlag{ Name: "var", Usage: "Set variable values when issuing `apply`", }, &cli.StringFlag{ Name: "module-version", Usage: "Use a specific module version to run the apply.", }, }, Action: func(c *cli.Context) error { varFlags := c.StringSlice("var") moduleVersion := c.String("module-version") var autoApprove *bool if c.IsSet("auto-approve") { val := c.Bool("auto-approve") autoApprove = &val } return BlockWorkspaceAction(c, func(ctx context.Context, cfg api.Config, stack types.Stack, block types.Block, env types.Environment, workspace types.Workspace) error { moduleSourceOverride := "" if moduleVersion != "" { moduleSourceOverride = fmt.Sprintf("%s@%s", block.ModuleSource, moduleVersion) } newRunConfig, err := runs.GetPromotion(cfg, workspace, moduleSourceOverride) if err != nil { return fmt.Errorf("error getting run configuration for apply: %w", err) } skipped, err := runs.SetRunConfigVars(newRunConfig, varFlags) if len(skipped) > 0 { fmt.Printf("[Warning] The following variables were skipped because they don't exist in the module: %s\n\n", strings.Join(skipped, ", ")) } if err != nil { return err } newRun, err := runs.Create(cfg, workspace, newRunConfig, autoApprove, false) if err != nil { return fmt.Errorf("error creating run: %w", err) } else if newRun == nil { return fmt.Errorf("unable to create run") } fmt.Fprintf(os.Stdout, "created apply run %q\n", newRun.Uid) fmt.Fprintln(os.Stdout, runs.GetBrowserUrl(cfg, workspace, *newRun)) if c.IsSet("wait") { return runs.StreamLogs(ctx, cfg, workspace, newRun) } return nil }) }, } }
View Source
var Apps = &cli.Command{ Name: "apps", Usage: "View and modify applications", UsageText: "nullstone apps [subcommand]", Subcommands: []*cli.Command{ AppsList, }, }
View Source
var AppsList = &cli.Command{ Name: "list", Usage: "List applications", UsageText: "nullstone apps list", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "detail", Aliases: []string{"d"}, }, }, Action: func(c *cli.Context) error { return ProfileAction(c, func(cfg api.Config) error { client := api.Client{Config: cfg} allApps, err := client.Apps().List() if err != nil { return fmt.Errorf("error listing applications: %w", err) } if c.IsSet("detail") { appDetails := make([]string, len(allApps)+1) appDetails[0] = "ID|Name|Reference|Category|Type|Module|Stack|Framework" for i, app := range allApps { var appCategory types.CategoryName var appType string if appModule, err := find.Module(cfg, app.ModuleSource); err == nil { appCategory = appModule.Category appType = appModule.Type } appDetails[i+1] = fmt.Sprintf("%d|%s|%s|%s|%s|%s|%s|%s", app.Id, app.Name, app.Reference, appCategory, appType, app.ModuleSource, app.StackName, app.Framework) } fmt.Println(columnize.Format(appDetails, columnize.DefaultConfig())) } else { for _, app := range allApps { fmt.Println(app.Name) } } return nil }) }, }
View Source
var BlockFlag = &cli.StringFlag{ Name: "block", Usage: "Set the block name.", EnvVars: []string{"NULLSTONE_BLOCK", "NULLSTONE_APP"}, Required: true, }
View Source
var Blocks = &cli.Command{ Name: "blocks", Usage: "View and modify blocks", UsageText: "nullstone blocks [subcommand]", Subcommands: []*cli.Command{ BlocksList, BlocksNew, }, }
View Source
var BlocksList = &cli.Command{ Name: "list", Usage: "List blocks", UsageText: "nullstone blocks list --stack=<stack>", Flags: []cli.Flag{ StackRequiredFlag, &cli.BoolFlag{ Name: "detail", Aliases: []string{"d"}, }, }, Action: func(c *cli.Context) error { return ProfileAction(c, func(cfg api.Config) error { client := api.Client{Config: cfg} stackName := c.String(StackRequiredFlag.Name) stack, err := client.StacksByName().Get(stackName) if err != nil { return fmt.Errorf("error looking for stack %q: %w", stackName, err) } else if stack == nil { return fmt.Errorf("stack %q does not exist in organization %q", stackName, cfg.OrgName) } allBlocks, err := client.Blocks().List(stack.Id) if err != nil { return fmt.Errorf("error listing blocks: %w", err) } if c.IsSet("detail") { appDetails := make([]string, len(allBlocks)+1) appDetails[0] = "ID|Type|Name|Reference|Category|Module Type|Module|Stack" for i, block := range allBlocks { var blockCategory types.CategoryName var blockType string if blockModule, err := find.Module(cfg, block.ModuleSource); err == nil { blockCategory = blockModule.Category blockType = blockModule.Type } appDetails[i+1] = fmt.Sprintf("%d|%s|%s|%s|%s|%s|%s|%s", block.Id, block.Type, block.Name, block.Reference, blockCategory, blockType, block.ModuleSource, block.StackName) } fmt.Println(columnize.Format(appDetails, columnize.DefaultConfig())) } else { for _, block := range allBlocks { fmt.Println(block.Name) } } return nil }) }, }
View Source
var BlocksNew = &cli.Command{ Name: "new", Usage: "Create block", UsageText: "nullstone blocks new --name=<name> --stack=<stack> --module=<module> [--connection=<connection>...]", Flags: []cli.Flag{ StackRequiredFlag, &cli.StringFlag{ Name: "name", Required: true, }, &cli.StringFlag{ Name: "module", Usage: `Specify the unique name of the module to use for this block. Example: nullstone/aws-network`, Required: true, }, &cli.StringSliceFlag{ Name: "connection", Usage: "Map the connection name on the module to the block name in the stack. Example: --connection network=network0", }, }, Action: func(c *cli.Context) error { return ProfileAction(c, func(cfg api.Config) error { client := api.Client{Config: cfg} stackName := c.String(StackRequiredFlag.Name) stack, err := client.StacksByName().Get(stackName) if err != nil { return fmt.Errorf("error looking for stack %q: %w", stackName, err) } else if stack == nil { return fmt.Errorf("stack %q does not exist in organization %q", stackName, cfg.OrgName) } name := c.String("name") moduleSource := c.String("module") if !strings.Contains(moduleSource, "/") { moduleSource = fmt.Sprintf("%s/%s", cfg.OrgName, moduleSource) } connectionSlice := c.StringSlice("connection") module, err := find.Module(cfg, moduleSource) if err != nil { return err } sort.Sort(sort.Reverse(module.Versions)) var latestModuleVersion *types.ModuleVersion if len(module.Versions) > 0 { latestModuleVersion = &module.Versions[0] } connections, parentBlocks, err := mapConnectionsToTargets(cfg, stack, connectionSlice) if err != nil { return err } if err := validateConnections(latestModuleVersion, connections); err != nil { return err } block := &types.Block{ OrgName: cfg.OrgName, StackId: stack.Id, Type: blockTypeFromModuleCategory(module.Category), Name: name, ModuleSource: moduleSource, ModuleSourceVersion: "latest", Connections: connections, ParentBlocks: parentBlocks, } if strings.HasPrefix(string(module.Category), "app") { app := &types.Application{ Block: *block, Repo: "", Framework: "other", } if newApp, err := client.Apps().Create(stack.Id, app); err != nil { return err } else if newApp != nil { fmt.Printf("created %s app\n", newApp.Name) } else { fmt.Println("unable to create app") } } else { if newBlock, err := client.Blocks().Create(stack.Id, block); err != nil { return err } else if newBlock != nil { fmt.Printf("created %q block\n", newBlock.Name) } else { fmt.Println("unable to create block") } } return nil }) }, }
View Source
var Configure = &cli.Command{ Name: "configure", Flags: []cli.Flag{ AddressFlag, ApiKeyFlag, }, Action: func(c *cli.Context) error { apiKey := c.String(ApiKeyFlag.Name) if apiKey == "" { fmt.Print("Enter API Key: ") rawApiKey, err := terminal.ReadPassword(int(syscall.Stdin)) if err != nil { return fmt.Errorf("error reading password: %w", err) } fmt.Println() apiKey = string(rawApiKey) } profile := config.Profile{ Name: GetProfile(c), Address: c.String(AddressFlag.Name), ApiKey: apiKey, } if err := profile.Save(); err != nil { return fmt.Errorf("error configuring profile: %w", err) } fmt.Fprintln(os.Stderr, "nullstone configured successfully!") return nil }, }
View Source
var Deploy = func(providers app.Providers) *cli.Command { return &cli.Command{ Name: "deploy", Usage: "Deploy application", UsageText: "nullstone deploy [--stack=<stack-name>] --app=<app-name> --env=<env-name> [options]", Flags: []cli.Flag{ StackFlag, AppFlag, OldEnvFlag, AppVersionFlag, &cli.BoolFlag{ Name: "wait", Aliases: []string{"w"}, }, }, Action: func(c *cli.Context) error { return AppWorkspaceAction(c, func(ctx context.Context, cfg api.Config, appDetails app.Details) error { version, wait := DetectAppVersion(c), c.IsSet("wait") if version == "" { return fmt.Errorf("no version specified, version is required to create a deploy") } deploy, err := CreateDeploy(cfg, appDetails, version) if err != nil { return err } return streamDeployLogs(ctx, cfg, *deploy, wait) }) }, } }
View Source
var EnvFlag = &cli.StringFlag{ Name: "env", Usage: `Set the environment name.`, EnvVars: []string{"NULLSTONE_ENV"}, Required: true, }
View Source
var EnvOptionalFlag = &cli.StringFlag{ Name: "env", Usage: `Set the environment name.`, EnvVars: []string{"NULLSTONE_ENV"}, Required: false, }
View Source
var Envs = &cli.Command{ Name: "envs", Usage: "View and modify environments", UsageText: "nullstone envs [subcommand]", Subcommands: []*cli.Command{ EnvsList, EnvsNew, }, }
View Source
var EnvsList = &cli.Command{ Name: "list", Usage: "List environments", UsageText: "nullstone envs list --stack=<stack-name>", Flags: []cli.Flag{ StackRequiredFlag, &cli.BoolFlag{ Name: "detail", Aliases: []string{"d"}, }, }, Action: func(c *cli.Context) error { return ProfileAction(c, func(cfg api.Config) error { stackName := c.String(StackRequiredFlag.Name) stack, err := find.Stack(cfg, stackName) if err != nil { return fmt.Errorf("error retrieving stack: %w", err) } else if stack == nil { return fmt.Errorf("stack %s does not exist", stackName) } client := api.Client{Config: cfg} envs, err := client.Environments().List(stack.Id) if err != nil { return fmt.Errorf("error listing environments: %w", err) } sort.SliceStable(envs, func(i, j int) bool { return envs[i].PipelineOrder < envs[i].PipelineOrder }) if c.IsSet("detail") { envDetails := make([]string, len(envs)+1) envDetails[0] = "ID|Name" for i, env := range envs { envDetails[i+1] = fmt.Sprintf("%d|%s", env.Id, env.Name) } fmt.Println(columnize.Format(envDetails, columnize.DefaultConfig())) } else { for _, env := range envs { fmt.Println(env.Name) } } return nil }) }, }
View Source
var EnvsNew = &cli.Command{ Name: "new", Usage: "Create new environment", UsageText: "nullstone envs new --name=<name> --stack=<stack> --provider=<provider>", Flags: []cli.Flag{ &cli.StringFlag{Name: "name", Required: true}, StackFlag, &cli.StringFlag{Name: "provider", Required: true}, &cli.StringFlag{Name: "region"}, &cli.StringFlag{Name: "zone"}, }, Action: func(c *cli.Context) error { return ProfileAction(c, func(cfg api.Config) error { client := api.Client{Config: cfg} name := c.String("name") stackName := c.String("stack") providerName := c.String("provider") stack, err := client.StacksByName().Get(stackName) if err != nil { return fmt.Errorf("error looking for stack %q: %w", stackName, err) } else if stack == nil { return fmt.Errorf("stack %q does not exist", stackName) } provider, err := client.Providers().Get(providerName) if err != nil { return fmt.Errorf("error looking for provider %q: %w", providerName, err) } else if provider == nil { return fmt.Errorf("provider %q does not exist", providerName) } pc := types.ProviderConfig{} switch provider.ProviderType { case "aws": pc.Aws = &types.AwsProviderConfig{ ProviderName: provider.Name, Region: c.String("region"), } if pc.Aws.Region == "" { pc.Aws.Region = awsDefaultRegion } case "gcp": pc.Gcp = &types.GcpProviderConfig{ ProviderName: provider.Name, Region: c.String("region"), Zone: c.String("zone"), } if pc.Gcp.Region == "" || pc.Gcp.Zone == "" { pc.Gcp.Region = gcpDefaultRegion pc.Gcp.Zone = gcpDefaultZone } default: return fmt.Errorf("CLI does not support provider type %q yet", provider.ProviderType) } env, err := client.Environments().Create(stack.Id, &types.Environment{ Name: name, ProviderConfig: pc, }) if err != nil { return fmt.Errorf("error creating stack: %w", err) } fmt.Printf("created %q environment\n", env.Name) return nil }) }, }
View Source
var (
ErrMissingOrg = errors.New("An organization has not been configured with this profile. See 'nullstone set-org -h' for more details.")
)
View Source
var Exec = func(providers admin.Providers) *cli.Command { return &cli.Command{ Name: "exec", Usage: "Execute command on running service. Defaults command to '/bin/sh' which acts as opening a shell to the running container.", UsageText: "nullstone exec [--stack=<stack-name>] --app=<app-name> --env=<env-name> [options] [command]", Flags: []cli.Flag{ StackFlag, AppFlag, EnvFlag, TaskFlag, }, 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) } return AppWorkspaceAction(c, func(ctx context.Context, cfg api.Config, appDetails app.Details) error { remoter, err := providers.FindRemoter(logging.StandardOsWriters{}, cfg, appDetails) if err != nil { return err } return remoter.Exec(ctx, task, cmd) }) }, } }
View Source
var Launch = func(providers app.Providers) *cli.Command { return &cli.Command{ Name: "launch", Usage: "Launch application (push + deploy + wait-healthy)", UsageText: "nullstone launch [--stack=<stack-name>] --app=<app-name> --env=<env-name> [options]", Flags: []cli.Flag{ StackFlag, AppFlag, OldEnvFlag, AppSourceFlag, AppVersionFlag, }, Action: func(c *cli.Context) error { return AppWorkspaceAction(c, func(ctx context.Context, cfg api.Config, appDetails app.Details) error { source, version := c.String("source"), DetectAppVersion(c) osWriters := logging.StandardOsWriters{} factory := providers.FindFactory(*appDetails.Module) if factory == nil { return fmt.Errorf("this app module is not supported") } err := push(ctx, cfg, appDetails, osWriters, factory, source, version) if err != nil { return err } deploy, err := CreateDeploy(cfg, appDetails, version) if err != nil { return err } return streamDeployLogs(ctx, cfg, *deploy, true) }) }, } }
Launch command performs push, deploy, and logs
View Source
var Logs = func(providers admin.Providers) *cli.Command { return &cli.Command{ Name: "logs", Usage: "Emit application logs", UsageText: "nullstone logs [--stack=<stack-name>] --app=<app-name> --env=<env-name> [options]", Flags: []cli.Flag{ StackFlag, AppFlag, OldEnvFlag, &cli.DurationFlag{ Name: "start-time", Aliases: []string{"s"}, DefaultText: "0s", Usage: ` Emit log events that occur after the specified start-time. This is a golang duration relative to the time the command is issued. Examples: '5s' (5 seconds ago), '1m' (1 minute ago), '24h' (24 hours ago) `, }, &cli.DurationFlag{ Name: "end-time", Aliases: []string{"e"}, Usage: ` Emit log events that occur before the specified end-time. This is a golang duration relative to the time the command is issued. Examples: '5s' (5 seconds ago), '1m' (1 minute ago), '24h' (24 hours ago) `, }, &cli.DurationFlag{ Name: "interval", DefaultText: "1s", Usage: `Set --interval to a golang duration to control how often to pull new log events. This will do nothing unless --tail is set. `, }, &cli.BoolFlag{ Name: "tail", Aliases: []string{"t"}, Usage: `Set tail to watch log events and emit as they are reported. Use --interval to control how often to query log events. This is off by default, command will exit as soon as current log events are emitted.`, }, }, Action: func(c *cli.Context) error { logStreamOptions := config.LogStreamOptions{ WatchInterval: -1 * time.Second, } if c.IsSet("start-time") { absoluteTime := time.Now().Add(-c.Duration("start-time")) logStreamOptions.StartTime = &absoluteTime } if c.IsSet("end-time") { absoluteTime := time.Now().Add(-c.Duration("end-time")) logStreamOptions.EndTime = &absoluteTime } if c.IsSet("tail") { logStreamOptions.WatchInterval = time.Duration(0) if c.IsSet("interval") { logStreamOptions.WatchInterval = c.Duration("interval") } } return AppWorkspaceAction(c, func(ctx context.Context, cfg api.Config, appDetails app.Details) error { logStreamer, err := providers.FindLogStreamer(logging.StandardOsWriters{}, cfg, appDetails) if err != nil { return err } return logStreamer.Stream(ctx, logStreamOptions) }) }, } }
View Source
var Modules = &cli.Command{ Name: "modules", Usage: "View and modify modules", UsageText: "nullstone modules [subcommand]", Subcommands: []*cli.Command{ ModulesGenerate, ModulesRegister, ModulesPublish, ModulesPackage, }, }
View Source
var ModulesGenerate = &cli.Command{ Name: "generate", Usage: "Generate new module manifest (and optionally register)", UsageText: "nullstone modules generate [--register]", Flags: []cli.Flag{ &cli.BoolFlag{Name: "register"}, }, Action: func(c *cli.Context) error { return ProfileAction(c, func(cfg api.Config) error { existing, _ := modules.ManifestFromFile(moduleManifestFilename) survey := &moduleSurvey{} manifest, err := survey.Ask(cfg, existing) if err != nil { return err } if err := manifest.WriteManifestToFile(moduleManifestFilename); err != nil { return err } fmt.Printf("generated module manifest file to %s\n", moduleManifestFilename) if err := modules.Generate(manifest); err != nil { return err } fmt.Printf("generated base Terraform\n") if c.IsSet("register") { module, err := modules.Register(cfg, manifest) if err != nil { return err } fmt.Printf("registered %s/%s\n", module.OrgName, module.Name) } return nil }) }, }
View Source
var ModulesPackage = &cli.Command{ Name: "package", Usage: "Package a module", UsageText: "nullstone modules package", Flags: []cli.Flag{}, Action: func(c *cli.Context) error { manifest, err := modules.ManifestFromFile(moduleManifestFilename) if err != nil { return err } tarballFilename, err := modules.Package(manifest, "") if err == nil { fmt.Printf("created module package %q\n", tarballFilename) } return err }, }
View Source
var ModulesPublish = &cli.Command{ Name: "publish", Usage: "Package and publish new version of a module", UsageText: "nullstone modules publish --version=<version>", Flags: []cli.Flag{ &cli.StringFlag{ Name: "version", Aliases: []string{"v"}, Usage: `Specify a semver version for the module. 'next-patch': Uses a version that bumps the patch component of the latest module version. 'next-build': Uses the latest version and appends +<build> using the short Git commit SHA. (Fails if not in a Git repository)`, Required: true, }, }, Action: func(c *cli.Context) error { return ProfileAction(c, func(cfg api.Config) error { version := c.String("version") manifest, err := modules.ManifestFromFile(moduleManifestFilename) if err != nil { return err } if version == "next-patch" { version, err = modules.NextPatch(cfg, manifest) if err != nil { return err } } if version == "next-build" { version, err = modules.NextPatch(cfg, manifest) if err != nil { return err } var commitSha string if hash, err := getCurrentCommitSha(); err == nil && len(hash) >= 8 { commitSha = hash[0:8] } else { return fmt.Errorf("Using --version=next-build requires a git repository with a commit. Cannot find commit SHA: %w", err) } version = fmt.Sprintf("%s+%s", version, commitSha) } version = strings.TrimPrefix(version, "v") if isValid := semver.IsValid(fmt.Sprintf("v%s", version)); !isValid { return fmt.Errorf("version %q is not a valid semver", version) } tarballFilename, err := modules.Package(manifest, version) if err != nil { return err } fmt.Fprintf(os.Stderr, "Created module package %q\n", tarballFilename) tarball, err := os.Open(tarballFilename) if err != nil { return err } defer tarball.Close() client := api.Client{Config: cfg} if err := client.Org(manifest.OrgName).ModuleVersions().Create(manifest.Name, version, tarball); err != nil { return err } fmt.Fprintf(os.Stderr, "Published %s/%s@%s\n", manifest.OrgName, manifest.Name, version) fmt.Fprintln(os.Stdout, version) return nil }) }, }
View Source
var ModulesRegister = &cli.Command{ Name: "register", Usage: "Register module from .nullstone/module.yml", UsageText: "nullstone modules register", Flags: []cli.Flag{}, Aliases: []string{"new"}, Action: func(c *cli.Context) error { return ProfileAction(c, func(cfg api.Config) error { manifest, err := modules.ManifestFromFile(moduleManifestFilename) if err != nil { return err } module, err := modules.Register(cfg, manifest) if err != nil { return err } fmt.Printf("registered %s/%s\n", module.OrgName, module.Name) return nil }) }, }
View Source
var OldEnvFlag = &cli.StringFlag{ Name: "env", Usage: `Set the environment name.`, EnvVars: []string{"NULLSTONE_ENV"}, }
View Source
var OrgFlag = &cli.StringFlag{ Name: "org", EnvVars: []string{"NULLSTONE_ORG"}, Usage: `Nullstone organization name used to contextualize API calls. If this flag is not specified, the nullstone CLI will use ~/.nullstone/<profile>/org file.`, }
OrgFlag defines a flag that the CLI uses
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
View Source
var Outputs = func() *cli.Command { return &cli.Command{ Name: "outputs", Usage: "Retrieve outputs", UsageText: "nullstone outputs [--stack=<stack-name>] --block=<block-name> --env=<env-name> [options]", Flags: []cli.Flag{ StackFlag, BlockFlag, EnvFlag, &cli.BoolFlag{ Name: "plain", }, }, Action: func(c *cli.Context) error { return BlockWorkspaceAction(c, func(ctx context.Context, cfg api.Config, stack types.Stack, block types.Block, env types.Environment, workspace types.Workspace) error { client := api.Client{Config: cfg} outputs, err := client.WorkspaceOutputs().GetLatest(stack.Id, block.Id, env.Id) if err != nil { return err } else if outputs == nil { outputs = &types.Outputs{} } encoder := json.NewEncoder(os.Stdout) encoder.SetIndent("", " ") if c.IsSet("plain") { stripped := map[string]any{} for key, output := range *outputs { stripped[key] = output.Value } encoder.Encode(stripped) } else { encoder.Encode(*outputs) } return nil }) }, } }
Outputs command retrieves outputs from a workspace (block+env)
View Source
var Plan = func() *cli.Command { return &cli.Command{ Name: "plan", Usage: "Runs a plan with a disapproval", UsageText: "nullstone plan [--stack=<stack-name>] --block=<block-name> --env=<env-name> [options]", Flags: []cli.Flag{ StackFlag, BlockFlag, EnvFlag, &cli.BoolFlag{ Name: "wait", Aliases: []string{"w"}, Usage: "Stream the Terraform logs while waiting for Nullstone to run the plan.", }, &cli.StringSliceFlag{ Name: "var", Usage: "Set variable values when issuing `plan`", }, &cli.StringFlag{ Name: "module-version", Usage: "Use a specific module version to run the plan.", }, }, Action: func(c *cli.Context) error { varFlags := c.StringSlice("var") moduleVersion := c.String("module-version") return BlockWorkspaceAction(c, func(ctx context.Context, cfg api.Config, stack types.Stack, block types.Block, env types.Environment, workspace types.Workspace) error { moduleSourceOverride := "" if moduleVersion != "" { moduleSourceOverride = fmt.Sprintf("%s@%s", block.ModuleSource, moduleVersion) } newRunConfig, err := runs.GetPromotion(cfg, workspace, moduleSourceOverride) if err != nil { return fmt.Errorf("error getting run configuration for plan: %w", err) } skipped, err := runs.SetRunConfigVars(newRunConfig, varFlags) if len(skipped) > 0 { fmt.Printf("[Warning] The following variables were skipped because they don't exist in the module: %s\n\n", strings.Join(skipped, ", ")) } if err != nil { return err } f := false newRun, err := runs.Create(cfg, workspace, newRunConfig, &f, false) if err != nil { return fmt.Errorf("error creating run: %w", err) } else if newRun == nil { return fmt.Errorf("unable to create run") } fmt.Fprintf(os.Stdout, "created plan run %q\n", newRun.Uid) fmt.Fprintln(os.Stdout, runs.GetBrowserUrl(cfg, workspace, *newRun)) if c.IsSet("wait") { err := runs.StreamLogs(ctx, cfg, workspace, newRun) if err == runs.ErrRunDisapproved { return nil } return err } return nil }) }, } }
View Source
var ProfileFlag = &cli.StringFlag{ Name: "profile", EnvVars: []string{"NULLSTONE_PROFILE"}, Value: "default", Usage: "Name of profile", }
View Source
var Push = func(providers app.Providers) *cli.Command { return &cli.Command{ Name: "push", Usage: "Push artifact", UsageText: "nullstone push [--stack=<stack-name>] --app=<app-name> --env=<env-name> [options]", Flags: []cli.Flag{ StackFlag, AppFlag, OldEnvFlag, AppSourceFlag, AppVersionFlag, }, Action: func(c *cli.Context) error { return AppWorkspaceAction(c, func(ctx context.Context, cfg api.Config, appDetails app.Details) error { source, version := c.String("source"), DetectAppVersion(c) osWriters := logging.StandardOsWriters{} provider := providers.FindFactory(*appDetails.Module) if provider == nil { return fmt.Errorf("push is not supported for this app") } return push(ctx, cfg, appDetails, osWriters, provider, source, version) }) }, } }
Push command performs a docker push to an authenticated image registry configured against an app/container
View Source
var SetOrg = &cli.Command{ Name: "set-org", Usage: "Set the organization for the CLI", UsageText: `Most Nullstone CLI commands require a configured nullstone organization to operate. This command will set the organization for the current profile. If you wish to set the organization per command, use the global --org flag instead.`, Flags: []cli.Flag{}, Action: func(c *cli.Context) error { profile, err := config.LoadProfile(GetProfile(c)) if err != nil { return err } if c.NArg() != 1 { return cli.ShowCommandHelp(c, "set-org") } orgName := c.Args().Get(0) if err := profile.SaveOrg(orgName); err != nil { return err } fmt.Fprintf(os.Stderr, "Organization set to %s for %s profile\n", orgName, profile.Name) return nil }, }
View Source
var Ssh = func(providers admin.Providers) *cli.Command { return &cli.Command{ Name: "ssh", Usage: "SSH into a running service. Use to forward ports from remote service or hosts.", UsageText: "nullstone ssh [--stack=<stack-name>] --app=<app-name> --env=<env-name> [options]", Flags: []cli.Flag{ StackFlag, AppFlag, EnvFlag, TaskFlag, &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) if err != nil { return fmt.Errorf("invalid format for --forward/-L: %w", err) } forwards = append(forwards, pf) } return AppWorkspaceAction(c, func(ctx context.Context, cfg api.Config, appDetails app.Details) error { remoter, err := providers.FindRemoter(logging.StandardOsWriters{}, cfg, appDetails) if err != nil { return err } return remoter.Ssh(ctx, task, forwards) }) }, } }
View Source
var StackFlag = &cli.StringFlag{ Name: "stack", Usage: `Set the stack name that owns the app/block. This is only required if multiple apps/blocks have the same name.`, EnvVars: []string{"NULLSTONE_STACK"}, }
View Source
var StackRequiredFlag = &cli.StringFlag{ Name: "stack", Usage: `Set the stack name that owns the app/block. This is only required if multiple apps/blocks have the same name.`, EnvVars: []string{"NULLSTONE_STACK"}, Required: true, }
View Source
var Stacks = &cli.Command{ Name: "stacks", Usage: "View and modify stacks", UsageText: "nullstone stacks [subcommand]", Subcommands: []*cli.Command{ StacksList, StacksNew, }, }
View Source
var StacksList = &cli.Command{ Name: "list", Usage: "List stacks", UsageText: "nullstone stacks list", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "detail", Aliases: []string{"d"}, }, }, Action: func(c *cli.Context) error { return ProfileAction(c, func(cfg api.Config) error { client := api.Client{Config: cfg} allStacks, err := client.Stacks().List() if err != nil { return fmt.Errorf("error listing stacks: %w", err) } if c.IsSet("detail") { stackDetails := make([]string, len(allStacks)+1) stackDetails[0] = "ID|Name|Description" for i, stack := range allStacks { stackDetails[i+1] = fmt.Sprintf("%d|%s|%s", stack.Id, stack.Name, stack.Description) } fmt.Println(columnize.Format(stackDetails, columnize.DefaultConfig())) } else { for _, stack := range allStacks { fmt.Println(stack.Name) } } return nil }) }, }
View Source
var StacksNew = &cli.Command{ Name: "new", Usage: "Create new stack", UsageText: "nullstone stacks new --name=<name> --description=<description>", Flags: []cli.Flag{ &cli.StringFlag{Name: "name", Required: true}, &cli.StringFlag{Name: "description", Required: true}, }, Action: func(c *cli.Context) error { return ProfileAction(c, func(cfg api.Config) error { client := api.Client{Config: cfg} name := c.String("name") description := c.String("description") stack, err := client.Stacks().Create(&types.Stack{ Name: name, Description: description, }) if err != nil { return fmt.Errorf("error creating stack: %w", err) } fmt.Printf("created %q stack\n", stack.Name) return nil }) }, }
View Source
var Status = func(providers admin.Providers) *cli.Command { return &cli.Command{ Name: "status", Usage: "Application Status", UsageText: "nullstone status [--stack=<stack-name>] --app=<app-name> [--env=<env-name>] [options]", Flags: []cli.Flag{ StackFlag, AppFlag, EnvOptionalFlag, AppVersionFlag, &cli.BoolFlag{ Name: "watch", Aliases: []string{"w"}, }, }, Action: func(c *cli.Context) error { watchInterval := -1 * time.Second if c.IsSet("watch") { watchInterval = defaultWatchInterval } return ProfileAction(c, func(cfg api.Config) error { return ParseAppEnv(c, false, func(stackName, appName, envName string) error { return CancellableAction(func(ctx context.Context) error { if envName == "" { return appStatus(ctx, cfg, providers, watchInterval, stackName, appName) } else { return appEnvStatus(ctx, cfg, providers, watchInterval, stackName, appName, envName) } }) }) }) }, } }
View Source
var TaskFlag = &cli.StringFlag{
Name: "task",
Usage: `Optionally, specify the task/replica to execute the command against.
If not specified, this will connect to a random task/replica.
If using Kubernetes, this will select which replica of the pod to connect.
If using ECS, this will select which task of the service to connect.`,
}
View Source
var Up = func() *cli.Command { return &cli.Command{ Name: "up", Usage: "Provisions the block and all of its dependencies", UsageText: "nullstone up [--stack=<stack-name>] --block=<block-name> --env=<env-name> [options]", Flags: []cli.Flag{ StackFlag, BlockFlag, EnvFlag, &cli.BoolFlag{ Name: "wait", Aliases: []string{"w"}, Usage: "Wait for Nullstone to fully provision the workspace.", }, &cli.StringSliceFlag{ Name: "var", Usage: "Set variable values when issuing `up`", }, }, Action: func(c *cli.Context) error { varFlags := c.StringSlice("var") return BlockWorkspaceAction(c, func(ctx context.Context, cfg api.Config, stack types.Stack, block types.Block, env types.Environment, workspace types.Workspace) error { if workspace.Status == types.WorkspaceStatusProvisioned { fmt.Println("workspace is already provisioned") return nil } newRunConfig, err := runs.GetPromotion(cfg, workspace, "") if err != nil { return err } skipped, err := runs.SetRunConfigVars(newRunConfig, varFlags) if len(skipped) > 0 { fmt.Printf("[Warning] The following variables were skipped because they don't exist in the module: %s\n\n", strings.Join(skipped, ", ")) } if err != nil { return err } t := true newRun, err := runs.Create(cfg, workspace, newRunConfig, &t, false) if err != nil { return fmt.Errorf("error creating run: %w", err) } else if newRun == nil { return fmt.Errorf("unable to create run") } fmt.Printf("created run %q\n", newRun.Uid) fmt.Fprintln(os.Stdout, runs.GetBrowserUrl(cfg, workspace, *newRun)) if c.IsSet("wait") { return runs.StreamLogs(ctx, cfg, workspace, newRun) } return nil }) }, } }
View Source
var Workspaces = &cli.Command{ Name: "workspaces", Usage: "View and modify workspaces", UsageText: "nullstone workspaces [subcommand]", Subcommands: []*cli.Command{ WorkspacesSelect, }, }
View Source
var WorkspacesSelect = &cli.Command{ Name: "select", Usage: "Select workspace", UsageText: "nullstone workspaces select [--stack=<stack>] --block=<block> --env=<env>", Flags: []cli.Flag{ StackFlag, &cli.StringFlag{ Name: "block", Required: true, }, &cli.StringFlag{ Name: "env", Required: true, }, }, Action: func(c *cli.Context) error { return ProfileAction(c, func(cfg api.Config) error { if !tfconfig.IsCredsConfigured(cfg) { if err := tfconfig.ConfigCreds(cfg); err != nil { fmt.Printf("Warning: unable to configure Terraform-based credentials with Nullstone servers: %s\n", err) } else { fmt.Println("Configured Terraform-based credentials with Nullstone servers.") } } client := api.Client{Config: cfg} stackName := c.String("stack") blockName := c.String("block") envName := c.String("env") sbe, err := find.StackBlockEnvByName(cfg, stackName, blockName, envName) if err != nil { return err } targetWorkspace := workspaces.Manifest{ OrgName: cfg.OrgName, StackId: sbe.Stack.Id, StackName: sbe.Stack.Name, BlockId: sbe.Block.Id, BlockName: sbe.Block.Name, BlockRef: sbe.Block.Reference, EnvId: sbe.Env.Id, EnvName: sbe.Env.Name, Connections: workspaces.ManifestConnections{}, } workspace, err := client.Workspaces().Get(targetWorkspace.StackId, targetWorkspace.BlockId, targetWorkspace.EnvId) if err != nil { return err } else if workspace == nil { return fmt.Errorf("could not find workspace (stack=%s, block=%s, env=%s)", stackName, blockName, envName) } targetWorkspace.WorkspaceUid = workspace.Uid.String() runConfig, err := workspaces.GetRunConfig(cfg, targetWorkspace) if err != nil { return fmt.Errorf("could not retreive current workspace configuration: %w", err) } manualConnections, err := surveyMissingConnections(cfg, targetWorkspace.StackName, runConfig) if err != nil { return err } for name, conn := range manualConnections { targetWorkspace.Connections[name] = workspaces.ManifestConnectionTarget{ StackId: conn.Reference.StackId, BlockId: conn.Reference.BlockId, BlockName: conn.Reference.BlockName, EnvId: conn.Reference.EnvId, } } return CancellableAction(func(ctx context.Context) error { return workspaces.Select(ctx, cfg, targetWorkspace, runConfig) }) }) }, }
Functions ¶
func AppWorkspaceAction ¶ added in v0.0.67
func AppWorkspaceAction(c *cli.Context, fn AppWorkspaceFn) error
func BlockWorkspaceAction ¶ added in v0.0.68
func BlockWorkspaceAction(c *cli.Context, fn BlockWorkspaceActionFn) error
func CancellableAction ¶ added in v0.0.26
func CreateDeploy ¶ added in v0.0.67
func DetectAppVersion ¶ added in v0.0.28
func DetectAppVersion(c *cli.Context) string
func FindAppDetails ¶ added in v0.0.67
FindAppDetails retrieves the app, env, and workspace stackName is optional -- If multiple apps are found, this will return an error
func GetEnvironment ¶ added in v0.0.43
func GetEnvironment(c *cli.Context) string
func GetProfile ¶ added in v0.0.4
func GetProfile(c *cli.Context) string
func ParseAppEnv ¶ added in v0.0.43
func ParseAppEnv(c *cli.Context, isEnvRequired bool, fn ParseAppEnvFn) error
func ProfileAction ¶ added in v0.0.26
func SetupProfileCmd ¶ added in v0.0.7
Types ¶
type AppWorkspaceFn ¶ added in v0.0.67
type AppWorkspaceInfo ¶ added in v0.0.26
type BlockWorkspaceActionFn ¶ added in v0.0.68
type NsStatus ¶ added in v0.0.26
type NsStatus struct {
Config api.Config
}
func (NsStatus) GetAppWorkspaceInfo ¶ added in v0.0.26
func (s NsStatus) GetAppWorkspaceInfo(application *types.Application, env *types.Environment) (AppWorkspaceInfo, error)
type ParseAppEnvFn ¶ added in v0.0.43
type TableBuffer ¶ added in v0.0.26
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
func (*TableBuffer) AddFields ¶ added in v0.0.26
func (b *TableBuffer) AddFields(fields ...string)
func (*TableBuffer) AddRow ¶ added in v0.0.26
func (b *TableBuffer) AddRow(data map[string]interface{})
func (*TableBuffer) String ¶ added in v0.0.26
func (b *TableBuffer) String() string
func (*TableBuffer) Values ¶ added in v0.0.26
func (b *TableBuffer) Values() [][]string
Source Files ¶
- app_env_context.go
- app_workspace_action.go
- apply.go
- apps.go
- block_workspace_action.go
- blocks.go
- cancellable_action.go
- configure.go
- create_deploy.go
- deploy.go
- envs.go
- exec.go
- find_app_details.go
- flag_app.go
- flag_app_source.go
- flag_app_version.go
- flag_block.go
- flag_env.go
- flag_org.go
- flag_profile.go
- flag_stack.go
- flag_task.go
- get_commit_sha.go
- launch.go
- logs.go
- module_survey.go
- modules.go
- ns_status.go
- outputs.go
- plan.go
- profile_action.go
- push.go
- set_org.go
- setup_profile_cmd.go
- ssh.go
- stacks.go
- status.go
- table_buffer.go
- up.go
- wait_healthy.go
- watch_action.go
- workspaces.go
Click to show internal directories.
Click to hide internal directories.