cmd

package
v0.0.53 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 10, 2022 License: MIT Imports: 33 Imported by: 0

Documentation

Index

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 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,
		},
		Action: func(c *cli.Context) error {
			return AppEnvAction(c, providers, func(ctx context.Context, cfg api.Config, provider app.Provider, details app.Details) error {
				userConfig := map[string]string{
					"version": DetectAppVersion(c),
				}
				return provider.Deploy(cfg, details, userConfig)
			})
		},
	}
}
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)
			finder := NsFinder{Config: cfg}
			stack, err := finder.FindStack(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 app.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 {
			userConfig := map[string]string{
				"task": c.String("task"),
				"cmd":  "/bin/sh",
			}
			if c.Args().Len() >= 1 {
				userConfig["cmd"] = c.Args().Get(c.Args().Len() - 1)
			}

			return AppEnvAction(c, providers, func(ctx context.Context, cfg api.Config, provider app.Provider, details app.Details) error {
				return provider.Exec(ctx, cfg, details, userConfig)
			})
		},
	}
}
View Source
var Launch = func(providers app.Providers, logProviders app_logs.Providers) *cli.Command {
	return &cli.Command{
		Name:      "launch",
		Usage:     "Launch application (push + deploy + log)",
		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 AppEnvAction(c, providers, func(ctx context.Context, cfg api.Config, provider app.Provider, details app.Details) error {
				logger := log.New(os.Stderr, "", 0)

				userConfig := map[string]string{
					"source":  c.String("source"),
					"version": DetectAppVersion(c),
				}

				logger.Println("Pushing app artifact...")
				if err := provider.Push(cfg, details, userConfig); err != nil {
					return fmt.Errorf("error pushing artifact: %w", err)
				}
				logger.Println()

				logger.Println("Deploying application...")
				if err := provider.Deploy(cfg, details, userConfig); err != nil {
					return fmt.Errorf("error deploying app: %w", err)
				}
				logger.Println()

				logger.Println("Tailing application logs...")
				logProvider, err := logProviders.Identify(provider.DefaultLogProvider(), cfg, details)
				if err != nil {
					return err
				}
				now := time.Now()
				return logProvider.Stream(ctx, cfg, details, config.LogStreamOptions{StartTime: &now, Out: os.Stdout})
			})
		},
	}
}

Launch command performs push, deploy, and logs

View Source
var Logs = func(providers app.Providers, logProviders app_logs.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,
				Out:           os.Stdout,
			}
			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 AppEnvAction(c, providers, func(ctx context.Context, cfg api.Config, provider app.Provider, details app.Details) error {
				logProvider, err := logProviders.Identify(provider.DefaultLogProvider(), cfg, details)
				if err != nil {
					return err
				}
				return logProvider.Stream(ctx, cfg, details, logStreamOptions)
			})
		},
	}
}
View Source
var Modules = &cli.Command{
	Name:      "modules",
	Usage:     "View and modify modules",
	UsageText: "nullstone modules [subcommand]",
	Subcommands: []*cli.Command{
		ModulesGenerate,
		ModulesNew,
		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 ModulesNew = &cli.Command{
	Name:      "new",
	Usage:     "Create new module from .nullstone/module.yml",
	UsageText: "nullstone modules new",
	Flags:     []cli.Flag{},
	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 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",
			Required: true,
		},
	},
	Action: func(c *cli.Context) error {
		return ProfileAction(c, func(cfg api.Config) error {
			version := c.String("version")
			if !strings.HasPrefix(version, "v") {
				version = "v" + version
			}
			if isValid := semver.IsValid(version); !isValid {
				return fmt.Errorf("version %q is not a valid semver", version)
			}

			manifest, err := modules.ManifestFromFile(moduleManifestFilename)
			if err != nil {
				return err
			}

			tarballFilename, err := modules.Package(manifest, version)
			if err != nil {
				return err
			}
			fmt.Printf("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.Printf("published %s/%s@%s\n", manifest.OrgName, manifest.Name, version)
			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,
		},
		Action: func(c *cli.Context) error {
			return BlockEnvAction(c, func(ctx context.Context, cfg api.Config, stack types.Stack, block types.Block, env types.Environment) 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{}
				}

				stripped := map[string]interface{}{}
				for key, output := range *outputs {
					stripped[key] = output.Value
				}

				encoder := json.NewEncoder(os.Stdout)
				encoder.SetIndent("", "  ")
				encoder.Encode(stripped)
				return nil
			})
		},
	}
}

Outputs command retrieves outputs from a workspace (block+env)

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 AppEnvAction(c, providers, func(ctx context.Context, cfg api.Config, provider app.Provider, details app.Details) error {
				userConfig := map[string]string{
					"source":  c.String("source"),
					"version": DetectAppVersion(c),
				}
				return provider.Push(cfg, details, userConfig)
			})
		},
	}
}

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 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 app.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 {
			return BlockEnvAction(c, func(ctx context.Context, cfg api.Config, stack types.Stack, block types.Block, env types.Environment) error {
				varFlags := c.StringSlice("var")

				client := api.Client{Config: cfg}
				workspace, err := client.Workspaces().Get(stack.Id, block.Id, env.Id)
				if err != nil {
					return fmt.Errorf("error looking for workspace: %w", err)
				} else if workspace == nil {
					return fmt.Errorf("workspace not found")
				}

				if workspace.Status == types.WorkspaceStatusProvisioned {
					fmt.Println("workspace is already provisioned")
					return nil
				}

				newRunConfig, err := client.PromotionConfigs().Get(workspace.StackId, workspace.BlockId, workspace.EnvId)
				if err != nil {
					return err
				}

				fillRunConfigVariables(newRunConfig)

				skipped, err := 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
				}

				isApproved := true
				input := types.CreateRunInput{
					IsDestroy:         false,
					IsApproved:        &isApproved,
					Source:            newRunConfig.Source,
					SourceVersion:     newRunConfig.SourceVersion,
					Variables:         newRunConfig.Variables,
					EnvVariables:      newRunConfig.EnvVariables,
					Connections:       newRunConfig.Connections,
					Capabilities:      newRunConfig.Capabilities,
					Providers:         newRunConfig.Providers,
					DependencyConfigs: newRunConfig.DependencyConfigs,
				}

				newRun, err := client.Runs().Create(workspace.StackId, workspace.Uid, input)
				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)

				if c.IsSet("wait") {
					return streamLiveLogs(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 AppEnvAction added in v0.0.26

func AppEnvAction(c *cli.Context, providers app.Providers, fn AppEnvActionFn) error

func BlockEnvAction added in v0.0.43

func BlockEnvAction(c *cli.Context, fn BlockEnvActionFn) error

func CancellableAction added in v0.0.26

func CancellableAction(fn func(ctx context.Context) error) error

func DetectAppVersion added in v0.0.28

func DetectAppVersion(c *cli.Context) string

func GetApp added in v0.0.43

func GetApp(c *cli.Context) string

func GetEnvironment added in v0.0.43

func GetEnvironment(c *cli.Context) string

func GetOrg added in v0.0.4

func GetOrg(c *cli.Context, profile config.Profile) 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 ParseBlockEnv added in v0.0.43

func ParseBlockEnv(c *cli.Context, fn ParseAppEnvFn) error

func ProfileAction added in v0.0.26

func ProfileAction(c *cli.Context, fn ProfileFn) error

func SetupProfileCmd added in v0.0.7

func SetupProfileCmd(c *cli.Context) (*config.Profile, api.Config, error)

func WatchAction added in v0.0.26

func WatchAction(ctx context.Context, watchInterval time.Duration, fn func(w io.Writer) error) error

Types

type AppEnvActionFn added in v0.0.26

type AppEnvActionFn func(ctx context.Context, cfg api.Config, provider app.Provider, details app.Details) error

type AppWorkspaceInfo added in v0.0.26

type AppWorkspaceInfo struct {
	AppDetails app.Details
	Status     string
	Version    string
}

type BlockEnvActionFn added in v0.0.43

type BlockEnvActionFn func(ctx context.Context, cfg api.Config, stack types.Stack, block types.Block, env types.Environment) error

type ErrMultipleAppsFound added in v0.0.11

type ErrMultipleAppsFound struct {
	AppName    string
	StackNames []string
}

func (ErrMultipleAppsFound) Error added in v0.0.11

func (e ErrMultipleAppsFound) Error() string

type NsFinder added in v0.0.11

type NsFinder struct {
	Config api.Config
}

NsFinder is an object that provides a consistent querying approach for nullstone objects through the CLI It provides nice error messages that are tailored for the user flow of CLI commands

func (NsFinder) FindAppAndStack added in v0.0.26

func (f NsFinder) FindAppAndStack(appName, stackName string) (*types.Application, *types.Stack, error)

func (NsFinder) FindAppDetails added in v0.0.26

func (f NsFinder) FindAppDetails(appName, stackName, envName string) (app.Details, error)

FindAppDetails retrieves the app, env, and workspace stackName is optional -- If multiple apps are found, this will return an error

func (NsFinder) FindStack added in v0.0.26

func (f NsFinder) FindStack(stackName string) (*types.Stack, error)

func (NsFinder) GetEnv added in v0.0.20

func (f NsFinder) GetEnv(stackId int64, envName string) (*types.Environment, error)

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 ParseAppEnvFn func(stackName, appName, envName string) error

type ParseBlockEnvFn added in v0.0.43

type ParseBlockEnvFn func(stackName, blockName, envName string) error

type ProfileFn added in v0.0.26

type ProfileFn func(cfg api.Config) error

type TableBuffer added in v0.0.26

type TableBuffer struct {
	Fields   []string
	HasField map[string]bool
	Data     []map[string]interface{}
}

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

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL