diff --git a/.golangci.yaml b/.golangci.yaml index 7a45a8a6..bbadb5ad 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -419,7 +419,7 @@ linters: # 'api' is a domain-appropriate package name for this layer; revive flags it as "meaningless" - linters: [revive] text: "var-naming: avoid meaningless package names" - path: "pkg/api/" + path: "internal/api/" # cmd/ uses "json"/"text" as CLI output-format literals; extracting them as constants adds no value - linters: [goconst] path: "^cmd/" diff --git a/cmd/application.go b/cmd/application.go index 3ad7f000..c4de1bfa 100644 --- a/cmd/application.go +++ b/cmd/application.go @@ -18,16 +18,16 @@ func NewCmdApp() *cobra.Command { Aliases: []string{"cfg"}, Deprecated: "This has been moved under `package`. This command will be removed in v2.", Args: cobra.ExactArgs(1), - RunE: runPkgConfigure, + RunE: runInstanceConfigure, } - appConfigureCmd.Flags().StringVarP(&pkgParamsPath, "params", "p", pkgParamsPath, "Path to params JSON file. This file supports bash interpolation.") + appConfigureCmd.Flags().StringVarP(&instanceParamsPath, "params", "p", instanceParamsPath, "Path to params JSON file. This file supports bash interpolation.") appDeployCmd := &cobra.Command{ Use: `deploy --`, Deprecated: "This has been moved under `package`. This command will be removed in v2.", Args: cobra.ExactArgs(1), - RunE: runPkgDeploy, + RunE: runInstanceDeploy, } appDeployCmd.Flags().StringP("message", "m", "", "Add a message when deploying") @@ -37,24 +37,24 @@ func NewCmdApp() *cobra.Command { Deprecated: "This has been moved under `package`. This command will be removed in v2.", Aliases: []string{"cfg"}, Args: cobra.ExactArgs(1), - RunE: runPkgPatch, + RunE: runInstancePatch, } - appPatchCmd.Flags().StringArrayVarP(&pkgPatchQueries, "set", "s", []string{}, "Sets a package parameter value using JQ expressions.") + appPatchCmd.Flags().StringArrayVarP(&instancePatchQueries, "set", "s", []string{}, "Sets a package parameter value using JQ expressions.") // app and infra are the same, lets reuse a get command/template here. - pkgGetCmd := &cobra.Command{ + appGetCmd := &cobra.Command{ Use: `get --`, Deprecated: "This has been moved under `package`. This command will be removed in v2.", Aliases: []string{"g"}, Args: cobra.ExactArgs(1), // Enforce exactly one argument - RunE: runPkgGet, + RunE: runInstanceGet, } appCmd.AddCommand(appDeployCmd) appCmd.AddCommand(appConfigureCmd) appCmd.AddCommand(appPatchCmd) - appCmd.AddCommand(pkgGetCmd) + appCmd.AddCommand(appGetCmd) return appCmd } diff --git a/cmd/artifact.go b/cmd/artifact.go index 18487f28..f5fed623 100644 --- a/cmd/artifact.go +++ b/cmd/artifact.go @@ -10,7 +10,7 @@ import ( "github.com/charmbracelet/glamour" "github.com/massdriver-cloud/mass/docs/helpdocs" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/artifact" artifactcmd "github.com/massdriver-cloud/mass/internal/commands/artifact" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" diff --git a/cmd/bundle.go b/cmd/bundle.go index 94a4143b..de979592 100644 --- a/cmd/bundle.go +++ b/cmd/bundle.go @@ -14,7 +14,7 @@ import ( "github.com/charmbracelet/glamour" "github.com/massdriver-cloud/mass/docs/helpdocs" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/bundle" "github.com/massdriver-cloud/mass/internal/cli" cmdbundle "github.com/massdriver-cloud/mass/internal/commands/bundle" diff --git a/cmd/credential.go b/cmd/credential.go index 127f105d..7dea67a1 100644 --- a/cmd/credential.go +++ b/cmd/credential.go @@ -5,7 +5,7 @@ import ( "fmt" "github.com/massdriver-cloud/mass/docs/helpdocs" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/cli" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" "github.com/spf13/cobra" diff --git a/cmd/definition.go b/cmd/definition.go index adb70e1d..898b43b8 100644 --- a/cmd/definition.go +++ b/cmd/definition.go @@ -13,7 +13,7 @@ import ( "github.com/charmbracelet/glamour" "github.com/massdriver-cloud/mass/docs/helpdocs" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/cli" "github.com/massdriver-cloud/mass/internal/definition" "github.com/massdriver-cloud/mass/internal/prettylogs" diff --git a/cmd/environment.go b/cmd/environment.go index 6770bc6e..ecb1ada2 100644 --- a/cmd/environment.go +++ b/cmd/environment.go @@ -11,7 +11,8 @@ import ( "github.com/charmbracelet/glamour" "github.com/massdriver-cloud/mass/docs/helpdocs" - "github.com/massdriver-cloud/mass/internal/api" + apiv0 "github.com/massdriver-cloud/mass/internal/api/v0" + "github.com/massdriver-cloud/mass/internal/api/v1" "github.com/massdriver-cloud/mass/internal/cli" "github.com/massdriver-cloud/mass/internal/commands/environment" @@ -57,13 +58,13 @@ func NewCmdEnvironment() *cobra.Command { } environmentCreateCmd := &cobra.Command{ - Use: "create [slug]", + Use: "create [ID]", Short: "Create an environment", Long: helpdocs.MustRender("environment/create"), Args: cobra.ExactArgs(1), RunE: runEnvironmentCreate, } - environmentCreateCmd.Flags().StringP("name", "n", "", "Environment name (defaults to slug if not provided)") + environmentCreateCmd.Flags().StringP("name", "n", "", "Environment name (defaults to ID if not provided)") environmentDefaultCmd := &cobra.Command{ Use: "default [environment] [artifact-id]", @@ -149,12 +150,12 @@ func runEnvironmentList(cmd *cobra.Command, args []string) error { return fmt.Errorf("error initializing massdriver client: %w", mdClientErr) } - environments, err := api.GetEnvironmentsByProject(ctx, mdClient, projectID) + environments, err := apiv0.GetEnvironmentsByProject(ctx, mdClient, projectID) if err != nil { return err } - tbl := cli.NewTable("ID/Slug", "Name", "Description", "Monthly $", "Daily $") + tbl := cli.NewTable("ID", "Name", "Description", "Monthly $", "Daily $") for _, env := range environments { monthly := "" @@ -207,22 +208,22 @@ func renderEnvironment(environment *api.Environment) error { func runEnvironmentCreate(cmd *cobra.Command, args []string) error { ctx := context.Background() - fullSlug := args[0] + fullID := args[0] name, err := cmd.Flags().GetString("name") if err != nil { return err } - // Parse project-env format: extract project and env slugs separately - parts := strings.Split(fullSlug, "-") + // Parse project-env format: extract project and env IDs separately + parts := strings.Split(fullID, "-") if len(parts) < 2 { - return fmt.Errorf("unable to determine project from slug %s (expected format: project-env)", fullSlug) + return fmt.Errorf("unable to determine project from ID %s (expected format: project-env)", fullID) } - projectIDOrSlug := parts[0] - envSlug := strings.Join(parts[1:], "-") + projectID := parts[0] + envID := strings.Join(parts[1:], "-") if name == "" { - name = envSlug + name = envID } mdClient, mdClientErr := client.New() @@ -230,15 +231,20 @@ func runEnvironmentCreate(cmd *cobra.Command, args []string) error { return fmt.Errorf("error initializing massdriver client: %w", mdClientErr) } - env, err := api.CreateEnvironment(ctx, mdClient, projectIDOrSlug, name, envSlug, "") + input := api.CreateEnvironmentInput{ + Id: envID, + Name: name, + } + + env, err := api.CreateEnvironment(ctx, mdClient, projectID, input) if err != nil { return err } - fmt.Printf("✅ Environment `%s` created successfully\n", fullSlug) - urlHelper, urlErr := api.NewURLHelper(ctx, mdClient) + fmt.Printf("✅ Environment `%s` created successfully\n", fullID) + urlHelper, urlErr := apiv0.NewURLHelper(ctx, mdClient) if urlErr == nil { - fmt.Printf("🔗 %s\n", urlHelper.EnvironmentURL(projectIDOrSlug, env.Slug)) + fmt.Printf("🔗 %s\n", urlHelper.EnvironmentURL(projectID, env.ID)) } return nil } @@ -256,19 +262,19 @@ func runEnvironmentDefault(cmd *cobra.Command, args []string) error { return fmt.Errorf("error initializing massdriver client: %w", mdClientErr) } - err := api.SetEnvironmentDefault(ctx, mdClient, environmentID, artifactID) + err := apiv0.SetEnvironmentDefault(ctx, mdClient, environmentID, artifactID) if err != nil { return err } - environment, err := api.GetEnvironment(ctx, mdClient, environmentID) + environment, err := apiv0.GetEnvironment(ctx, mdClient, environmentID) if err != nil { return fmt.Errorf("failed to get environment: %w", err) } fullEnvSlug := fmt.Sprintf("%s-%s", environment.Project.Slug, environment.Slug) fmt.Printf("✅ Environment `%s` default connection set successfully\n", fullEnvSlug) - urlHelper, urlErr := api.NewURLHelper(ctx, mdClient) + urlHelper, urlErr := apiv0.NewURLHelper(ctx, mdClient) if urlErr == nil { fmt.Printf("🔗 %s\n", urlHelper.EnvironmentURL(environment.Project.Slug, environment.Slug)) } diff --git a/cmd/infrastructure.go b/cmd/infrastructure.go index e729bf40..9acc686e 100644 --- a/cmd/infrastructure.go +++ b/cmd/infrastructure.go @@ -17,16 +17,16 @@ func NewCmdInfra() *cobra.Command { Aliases: []string{"cfg"}, Deprecated: "This has been moved under `package`. This command will be removed in v2.", Args: cobra.ExactArgs(1), - RunE: runPkgConfigure, + RunE: runInstanceConfigure, } - infraConfigureCmd.Flags().StringVarP(&pkgParamsPath, "params", "p", pkgParamsPath, "Path to params JSON file. This file supports bash interpolation.") + infraConfigureCmd.Flags().StringVarP(&instanceParamsPath, "params", "p", instanceParamsPath, "Path to params JSON file. This file supports bash interpolation.") infraDeployCmd := &cobra.Command{ Use: `deploy --`, Deprecated: "This has been moved under `package`. This command will be removed in v2.", Args: cobra.ExactArgs(1), - RunE: runPkgDeploy, + RunE: runInstanceDeploy, } infraDeployCmd.Flags().StringP("message", "m", "", "Add a message when deploying") @@ -36,24 +36,24 @@ func NewCmdInfra() *cobra.Command { Deprecated: "This has been moved under `package`. This command will be removed in v2.", Aliases: []string{"cfg"}, Args: cobra.ExactArgs(1), - RunE: runPkgPatch, + RunE: runInstancePatch, } - infraPatchCmd.Flags().StringArrayVarP(&pkgPatchQueries, "set", "s", []string{}, "Sets a package parameter value using JQ expressions.") + infraPatchCmd.Flags().StringArrayVarP(&instancePatchQueries, "set", "s", []string{}, "Sets a package parameter value using JQ expressions.") // app and infra are the same, lets reuse a get command/template here. - pkgGetCmd := &cobra.Command{ + infraGetCmd := &cobra.Command{ Use: `get --`, Deprecated: "This has been moved under `package`. This command will be removed in v2.", Aliases: []string{"g"}, Args: cobra.ExactArgs(1), // Enforce exactly one argument - RunE: runPkgGet, + RunE: runInstanceGet, } infraCmd.AddCommand(infraConfigureCmd) infraCmd.AddCommand(infraDeployCmd) infraCmd.AddCommand(infraPatchCmd) - infraCmd.AddCommand(pkgGetCmd) + infraCmd.AddCommand(infraGetCmd) return infraCmd } diff --git a/cmd/instance.go b/cmd/instance.go new file mode 100644 index 00000000..5698019e --- /dev/null +++ b/cmd/instance.go @@ -0,0 +1,577 @@ +package cmd + +import ( + "bufio" + "bytes" + "context" + "embed" + "encoding/json" + "fmt" + "os" + "strings" + "text/template" + + "github.com/massdriver-cloud/mass/docs/helpdocs" + apiv0 "github.com/massdriver-cloud/mass/internal/api/v0" + "github.com/massdriver-cloud/mass/internal/cli" + "github.com/massdriver-cloud/mass/internal/commands/instance" + "github.com/massdriver-cloud/mass/internal/files" + "github.com/massdriver-cloud/mass/internal/prettylogs" + + "github.com/charmbracelet/glamour" + "github.com/charmbracelet/lipgloss" + "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" + "github.com/spf13/cobra" +) + +var ( + instanceParamsPath = "./params.json" + instancePatchQueries []string +) + +//go:embed templates/instance.get.md.tmpl +var instanceTemplates embed.FS + +// NewCmdInstance returns a cobra command for managing instances of IaC deployed in environments. +func NewCmdInstance() *cobra.Command { //nolint:funlen // cobra command builders are necessarily long + instanceCmd := &cobra.Command{ + Use: "instance", + Aliases: []string{"inst", "package", "instance"}, + Short: "Manage instances of IaC deployed in environments.", + Long: helpdocs.MustRender("instance"), + } + + instanceConfigureCmd := &cobra.Command{ + Use: `configure --`, + Short: "Configure instance", + Aliases: []string{"cfg"}, + Example: `mass instance configure ecomm-prod-vpc --params=params.json`, + Long: helpdocs.MustRender("instance/configure"), + Args: cobra.ExactArgs(1), + RunE: runInstanceConfigure, + } + instanceConfigureCmd.Flags().StringVarP(&instanceParamsPath, "params", "p", instanceParamsPath, "Path to params json, tfvars or yaml file. Use '-' to read from stdin. This file supports bash interpolation.") + + instanceDeployCmd := &cobra.Command{ + Use: `deploy --`, + Short: "Deploy instances", + Example: `mass instance deploy ecomm-prod-vpc`, + Long: helpdocs.MustRender("instance/deploy"), + Args: cobra.ExactArgs(1), + RunE: runInstanceDeploy, + } + instanceDeployCmd.Flags().StringP("message", "m", "", "Add a message when deploying") + + instanceExportCmd := &cobra.Command{ + Use: `export --`, + Short: "Export instances", + Example: `mass instance export ecomm-prod-vpc`, + Long: helpdocs.MustRender("instance/export"), + Args: cobra.ExactArgs(1), + RunE: runInstanceExport, + } + + // instance and infra are the same, lets reuse a get command/template here. + instanceGetCmd := &cobra.Command{ + Use: `get --`, + Short: "Get an instance", + Aliases: []string{"g"}, + Example: `mass instance get ecomm-prod-vpc`, + Long: helpdocs.MustRender("instance/get"), + Args: cobra.ExactArgs(1), + RunE: runInstanceGet, + } + instanceGetCmd.Flags().StringP("output", "o", "text", "Output format (text or json)") + + instancePatchCmd := &cobra.Command{ + Use: `patch --`, + Short: "Patch individual instance parameter values", + Example: `mass instance patch ecomm-prod-db --set='.version = "13.4"'`, + Long: helpdocs.MustRender("instance/patch"), + Args: cobra.ExactArgs(1), + RunE: runInstancePatch, + } + instancePatchCmd.Flags().StringArrayVarP(&instancePatchQueries, "set", "s", []string{}, "Sets an instance parameter value using JQ expressions.") + + instanceCreateCmd := &cobra.Command{ + Use: `create [slug]`, + Short: "Create a manifest (add bundle to project)", + Example: `mass instance create dbbundle-test-serverless --bundle aws-rds-cluster`, + Long: helpdocs.MustRender("instance/create"), + Args: cobra.ExactArgs(1), + RunE: runPkgCreate, + } + instanceCreateCmd.Flags().StringP("name", "n", "", "Manifest name (defaults to slug if not provided)") + instanceCreateCmd.Flags().StringP("bundle", "b", "", "Bundle ID or name (required)") + _ = instanceCreateCmd.MarkFlagRequired("bundle") + + instanceVersionCmd := &cobra.Command{ + Use: `version @`, + Short: "Set instance version", + Example: `mass instance version api-prod-db@latest --release-channel development`, + Long: helpdocs.MustRender("instance/version"), + Args: cobra.ExactArgs(1), + RunE: runInstanceVersion, + } + instanceVersionCmd.Flags().String("release-channel", "stable", "Release strategy (stable or development)") + + instanceDestroyCmd := &cobra.Command{ + Use: `destroy --`, + Short: "Destroy (decommission) an instance", + Example: `mass instance destroy api-prod-db --force`, + Long: "Destroy (decommission) an instance. This will permanently delete the instance and all its resources.", + Args: cobra.ExactArgs(1), + RunE: runInstanceDestroy, + } + instanceDestroyCmd.Flags().BoolP("force", "f", false, "Skip confirmation prompt") + + instanceResetCmd := &cobra.Command{ + Use: `reset --`, + Short: "Reset instance status to 'Initialized'", + Example: `mass instance reset api-prod-db`, + Long: helpdocs.MustRender("instance/reset"), + Args: cobra.ExactArgs(1), + RunE: runInstanceReset, + } + instanceResetCmd.Flags().BoolP("force", "f", false, "Skip confirmation prompt") + + instanceListCmd := &cobra.Command{ + Use: `list -`, + Short: "List instances in an environment", + Aliases: []string{"ls"}, + Example: `mass instance list ecomm-prod`, + Long: helpdocs.MustRender("instance/list"), + Args: cobra.ExactArgs(1), + RunE: runInstanceList, + } + + instanceCmd.AddCommand(instanceConfigureCmd) + instanceCmd.AddCommand(instanceDeployCmd) + instanceCmd.AddCommand(instanceExportCmd) + instanceCmd.AddCommand(instanceGetCmd) + instanceCmd.AddCommand(instanceListCmd) + instanceCmd.AddCommand(instancePatchCmd) + instanceCmd.AddCommand(instanceCreateCmd) + instanceCmd.AddCommand(instanceVersionCmd) + instanceCmd.AddCommand(instanceDestroyCmd) + instanceCmd.AddCommand(instanceResetCmd) + + return instanceCmd +} + +func runInstanceGet(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + outputFormat, err := cmd.Flags().GetString("output") + if err != nil { + return err + } + + instanceID := args[0] + + mdClient, mdClientErr := client.New() + if mdClientErr != nil { + return fmt.Errorf("error initializing massdriver client: %w", mdClientErr) + } + + instance, err := apiv0.GetPackage(ctx, mdClient, instanceID) + if err != nil { + return err + } + + switch outputFormat { + case "json": + jsonBytes, marshalErr := json.MarshalIndent(instance, "", " ") + if marshalErr != nil { + return fmt.Errorf("failed to marshal instance to JSON: %w", marshalErr) + } + fmt.Println(string(jsonBytes)) + case "text": + err = renderInstance(instance) + if err != nil { + return err + } + default: + return fmt.Errorf("unsupported output format: %s", outputFormat) + } + + return nil +} + +func renderInstance(instance *apiv0.Package) error { + tmplBytes, err := instanceTemplates.ReadFile("templates/instance.get.md.tmpl") + if err != nil { + return fmt.Errorf("failed to read template: %w", err) + } + + funcMap := template.FuncMap{ + "deref": func(s *string) string { + if s == nil { + return "" + } + return *s + }, + } + tmpl, err := template.New("instance").Funcs(funcMap).Parse(string(tmplBytes)) + if err != nil { + return fmt.Errorf("failed to parse template: %w", err) + } + + var buf bytes.Buffer + if renderErr := tmpl.Execute(&buf, instance); renderErr != nil { + return fmt.Errorf("failed to execute template: %w", renderErr) + } + + r, err := glamour.NewTermRenderer(glamour.WithAutoStyle()) + if err != nil { + return err + } + + out, err := r.Render(buf.String()) + if err != nil { + return err + } + + fmt.Print(out) + return nil +} + +func runInstanceDeploy(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + name := args[0] + + msg, err := cmd.Flags().GetString("message") + if err != nil { + return err + } + + mdClient, mdClientErr := client.New() + if mdClientErr != nil { + return fmt.Errorf("error initializing massdriver client: %w", mdClientErr) + } + + _, err = instance.RunDeploy(ctx, mdClient, name, msg) + + return err +} + +func runInstanceConfigure(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + instanceID := args[0] + + params := map[string]any{} + if instanceParamsPath == "-" { + // Read from stdin + if err := json.NewDecoder(os.Stdin).Decode(¶ms); err != nil { + return fmt.Errorf("failed to decode JSON from stdin: %w", err) + } + } else { + if err := files.Read(instanceParamsPath, ¶ms); err != nil { + return err + } + } + + mdClient, mdClientErr := client.New() + if mdClientErr != nil { + return fmt.Errorf("error initializing massdriver client: %w", mdClientErr) + } + + configuredInstance, err := instance.RunConfigure(ctx, mdClient, instanceID, params) + if err != nil { + return err + } + + fmt.Printf("✅ Instance `%s` configured successfully\n", configuredInstance.ID) + + // Get instance details to build URL + instanceDetails, err := apiv0.GetPackage(ctx, mdClient, configuredInstance.ID) + if err == nil && instanceDetails.Environment != nil && instanceDetails.Environment.Project != nil && instanceDetails.Manifest != nil { + urlHelper, urlErr := apiv0.NewURLHelper(ctx, mdClient) + if urlErr == nil { + fmt.Printf("🔗 %s\n", urlHelper.InstanceURL(instanceDetails.Environment.Project.ID, instanceDetails.Environment.ID, instanceDetails.Manifest.ID)) + } + } + + return nil +} + +func runInstancePatch(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + instanceID := args[0] + + cmd.SilenceUsage = true + + mdClient, mdClientErr := client.New() + if mdClientErr != nil { + return fmt.Errorf("error initializing massdriver client: %w", mdClientErr) + } + + _, err := instance.RunPatch(ctx, mdClient, instanceID, instancePatchQueries) + + var name = lipgloss.NewStyle().SetString(instanceID).Foreground(lipgloss.Color("#7D56F4")) + msg := fmt.Sprintf("Patching: %s", name) + fmt.Println(msg) + + return err +} + +func runInstanceExport(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + instanceID := args[0] + + cmd.SilenceUsage = true + + mdClient, mdClientErr := client.New() + if mdClientErr != nil { + return fmt.Errorf("error initializing massdriver client: %w", mdClientErr) + } + + exportErr := instance.RunExport(ctx, mdClient, instanceID) + if exportErr != nil { + return fmt.Errorf("failed to export instance: %w", exportErr) + } + + return nil +} + +func runPkgCreate(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + fullID := args[0] + name, err := cmd.Flags().GetString("name") + if err != nil { + return err + } + + bundleIDOrName, err := cmd.Flags().GetString("bundle") + if err != nil { + return err + } + + // Parse project-env-manifest format: extract project (first), env (second), and manifest (third) + // Format is $proj-$env-$manifest where each part has no hyphens + parts := strings.Split(fullID, "-") + if len(parts) != 3 { + return fmt.Errorf("unable to determine project, environment, and manifest from slug %s (expected format: project-env-manifest)", fullID) + } + projectIDOrID := parts[0] + environmentID := parts[1] + manifestID := parts[2] + + if name == "" { + name = manifestID + } + + mdClient, mdClientErr := client.New() + if mdClientErr != nil { + return fmt.Errorf("error initializing massdriver client: %w", mdClientErr) + } + + _, err = apiv0.CreateManifest(ctx, mdClient, bundleIDOrName, projectIDOrID, name, manifestID, "") + if err != nil { + return err + } + + fmt.Printf("✅ Instance `%s` created successfully\n", fullID) + urlHelper, urlErr := apiv0.NewURLHelper(ctx, mdClient) + if urlErr == nil { + fmt.Printf("🔗 %s\n", urlHelper.InstanceURL(projectIDOrID, environmentID, manifestID)) + } + return nil +} + +func runInstanceVersion(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + instanceIDAndVersion := args[0] + + // Parse instance-id@version format + parts := strings.Split(instanceIDAndVersion, "@") + if len(parts) != 2 { + return fmt.Errorf("invalid format: expected @, got %s", instanceIDAndVersion) + } + instanceID := parts[0] + version := parts[1] + + releaseChannel, err := cmd.Flags().GetString("release-channel") + if err != nil { + return err + } + + // Convert release channel to ReleaseStrategy enum value + var releaseStrategy apiv0.ReleaseStrategy + switch releaseChannel { + case "development": + releaseStrategy = apiv0.ReleaseStrategyDevelopment + case "stable": + releaseStrategy = apiv0.ReleaseStrategyStable + default: + return fmt.Errorf("invalid release-channel: must be 'stable' or 'development', got '%s'", releaseChannel) + } + + mdClient, mdClientErr := client.New() + if mdClientErr != nil { + return fmt.Errorf("error initializing massdriver client: %w", mdClientErr) + } + + updatedPkg, err := apiv0.SetPackageVersion(ctx, mdClient, instanceID, version, releaseStrategy) + if err != nil { + return err + } + + fmt.Printf("✅ Instance `%s` version set successfully\n", updatedPkg.ID) + + // Get instance details to build URL + instanceDetails, err := apiv0.GetPackage(ctx, mdClient, updatedPkg.ID) + if err == nil && instanceDetails.Environment != nil && instanceDetails.Environment.Project != nil && instanceDetails.Manifest != nil { + urlHelper, urlErr := apiv0.NewURLHelper(ctx, mdClient) + if urlErr == nil { + fmt.Printf("🔗 %s\n", urlHelper.InstanceURL(instanceDetails.Environment.Project.ID, instanceDetails.Environment.ID, instanceDetails.Manifest.ID)) + } + } + + return nil +} + +func runInstanceDestroy(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + instanceID := args[0] + force, err := cmd.Flags().GetBool("force") + if err != nil { + return err + } + + cmd.SilenceUsage = true + + mdClient, mdClientErr := client.New() + if mdClientErr != nil { + return fmt.Errorf("error initializing massdriver client: %w", mdClientErr) + } + + // Get instance details for confirmation and URL + instance, err := apiv0.GetPackage(ctx, mdClient, instanceID) + if err != nil { + return err + } + + // Prompt for confirmation - requires typing the instance slug unless --force is used + if !force { + fmt.Printf("WARNING: This will permanently decommission instance `%s` and all its resources.\n", instance.ID) + fmt.Printf("Type `%s` to confirm decommission: ", instance.ID) + reader := bufio.NewReader(os.Stdin) + answer, _ := reader.ReadString('\n') + answer = strings.TrimSpace(answer) + + if answer != instance.ID { + fmt.Println("Decommission cancelled.") + return nil + } + } + + _, err = apiv0.DecommissionPackage(ctx, mdClient, instance.ID, "") + if err != nil { + return err + } + + fmt.Printf("✅ Instance `%s` decommission started\n", instance.ID) + + // Get instance details to build URL + if instance.Environment != nil && instance.Environment.Project != nil && instance.Manifest != nil { + urlHelper, urlErr := apiv0.NewURLHelper(ctx, mdClient) + if urlErr == nil { + fmt.Printf("🔗 %s\n", urlHelper.InstanceURL(instance.Environment.Project.ID, instance.Environment.ID, instance.Manifest.ID)) + } + } + + return nil +} + +func runInstanceReset(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + instanceID := args[0] + + force, err := cmd.Flags().GetBool("force") + if err != nil { + return err + } + + cmd.SilenceUsage = true + + mdClient, mdClientErr := client.New() + if mdClientErr != nil { + return fmt.Errorf("error initializing massdriver client: %w", mdClientErr) + } + + // Get instance details for confirmation + instanceDetails, err := apiv0.GetPackage(ctx, mdClient, instanceID) + if err != nil { + return err + } + + // Prompt for confirmation unless --force is used + if !force { + fmt.Printf("%s: This will reset instance `%s` to 'Initialized' state and delete deployment history.\n", prettylogs.Orange("WARNING"), instanceDetails.ID) + fmt.Printf("Type `%s` to confirm reset: ", instanceDetails.ID) + reader := bufio.NewReader(os.Stdin) + answer, _ := reader.ReadString('\n') + answer = strings.TrimSpace(answer) + + if answer != instanceDetails.ID { + fmt.Println("Reset cancelled.") + return nil + } + } + + instance, err := instance.RunReset(ctx, mdClient, instanceID) + if err != nil { + return err + } + + var name = lipgloss.NewStyle().SetString(instance.ID).Foreground(lipgloss.Color("#7D56F4")) + msg := fmt.Sprintf("✅ Instance %s reset successfully", name) + fmt.Println(msg) + + return nil +} + +func runInstanceList(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + environmentID := args[0] + + cmd.SilenceUsage = true + + mdClient, mdClientErr := client.New() + if mdClientErr != nil { + return fmt.Errorf("error initializing massdriver client: %w", mdClientErr) + } + + env, err := apiv0.GetEnvironment(ctx, mdClient, environmentID) + if err != nil { + return err + } + + tbl := cli.NewTable("ID", "Name", "Bundle", "Status") + + for _, p := range env.Packages { + name := "" + if p.Manifest != nil { + name = p.Manifest.Name + } + bundleName := "" + if p.Bundle != nil { + bundleName = p.Bundle.Name + } + tbl.AddRow(p.ID, name, bundleName, p.Status) + } + + tbl.Print() + + return nil +} diff --git a/cmd/logs.go b/cmd/logs.go index 6d30eec2..b3e04b06 100644 --- a/cmd/logs.go +++ b/cmd/logs.go @@ -6,7 +6,7 @@ import ( "os" "github.com/massdriver-cloud/mass/docs/helpdocs" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" "github.com/spf13/cobra" ) diff --git a/cmd/package.go b/cmd/package.go deleted file mode 100644 index 79ed3ded..00000000 --- a/cmd/package.go +++ /dev/null @@ -1,577 +0,0 @@ -package cmd - -import ( - "bufio" - "bytes" - "context" - "embed" - "encoding/json" - "fmt" - "os" - "strings" - "text/template" - - "github.com/massdriver-cloud/mass/docs/helpdocs" - "github.com/massdriver-cloud/mass/internal/api" - "github.com/massdriver-cloud/mass/internal/cli" - "github.com/massdriver-cloud/mass/internal/commands/pkg" - "github.com/massdriver-cloud/mass/internal/files" - "github.com/massdriver-cloud/mass/internal/prettylogs" - - "github.com/charmbracelet/glamour" - "github.com/charmbracelet/lipgloss" - "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" - "github.com/spf13/cobra" -) - -var ( - pkgParamsPath = "./params.json" - pkgPatchQueries []string -) - -//go:embed templates/package.get.md.tmpl -var packageTemplates embed.FS - -// NewCmdPkg returns a cobra command for managing packages of IaC deployed in environments. -func NewCmdPkg() *cobra.Command { //nolint:funlen // cobra command builders are necessarily long - pkgCmd := &cobra.Command{ - Use: "package", - Aliases: []string{"pkg"}, - Short: "Manage packages of IaC deployed in environments.", - Long: helpdocs.MustRender("package"), - } - - pkgConfigureCmd := &cobra.Command{ - Use: `configure --`, - Short: "Configure package", - Aliases: []string{"cfg"}, - Example: `mass package configure ecomm-prod-vpc --params=params.json`, - Long: helpdocs.MustRender("package/configure"), - Args: cobra.ExactArgs(1), - RunE: runPkgConfigure, - } - pkgConfigureCmd.Flags().StringVarP(&pkgParamsPath, "params", "p", pkgParamsPath, "Path to params json, tfvars or yaml file. Use '-' to read from stdin. This file supports bash interpolation.") - - pkgDeployCmd := &cobra.Command{ - Use: `deploy --`, - Short: "Deploy packages", - Example: `mass package deploy ecomm-prod-vpc`, - Long: helpdocs.MustRender("package/deploy"), - Args: cobra.ExactArgs(1), - RunE: runPkgDeploy, - } - pkgDeployCmd.Flags().StringP("message", "m", "", "Add a message when deploying") - - pkgExportCmd := &cobra.Command{ - Use: `export --`, - Short: "Export packages", - Example: `mass package export ecomm-prod-vpc`, - Long: helpdocs.MustRender("package/export"), - Args: cobra.ExactArgs(1), - RunE: runPkgExport, - } - - // pkg and infra are the same, lets reuse a get command/template here. - pkgGetCmd := &cobra.Command{ - Use: `get --`, - Short: "Get a package", - Aliases: []string{"g"}, - Example: `mass package get ecomm-prod-vpc`, - Long: helpdocs.MustRender("package/get"), - Args: cobra.ExactArgs(1), - RunE: runPkgGet, - } - pkgGetCmd.Flags().StringP("output", "o", "text", "Output format (text or json)") - - pkgPatchCmd := &cobra.Command{ - Use: `patch --`, - Short: "Patch individual package parameter values", - Example: `mass package patch ecomm-prod-db --set='.version = "13.4"'`, - Long: helpdocs.MustRender("package/patch"), - Args: cobra.ExactArgs(1), - RunE: runPkgPatch, - } - pkgPatchCmd.Flags().StringArrayVarP(&pkgPatchQueries, "set", "s", []string{}, "Sets a package parameter value using JQ expressions.") - - pkgCreateCmd := &cobra.Command{ - Use: `create [slug]`, - Short: "Create a manifest (add bundle to project)", - Example: `mass package create dbbundle-test-serverless --bundle aws-rds-cluster`, - Long: helpdocs.MustRender("package/create"), - Args: cobra.ExactArgs(1), - RunE: runPkgCreate, - } - pkgCreateCmd.Flags().StringP("name", "n", "", "Manifest name (defaults to slug if not provided)") - pkgCreateCmd.Flags().StringP("bundle", "b", "", "Bundle ID or name (required)") - _ = pkgCreateCmd.MarkFlagRequired("bundle") - - pkgVersionCmd := &cobra.Command{ - Use: `version @`, - Short: "Set package version", - Example: `mass package version api-prod-db@latest --release-channel development`, - Long: helpdocs.MustRender("package/version"), - Args: cobra.ExactArgs(1), - RunE: runPkgVersion, - } - pkgVersionCmd.Flags().String("release-channel", "stable", "Release strategy (stable or development)") - - pkgDestroyCmd := &cobra.Command{ - Use: `destroy --`, - Short: "Destroy (decommission) a package", - Example: `mass package destroy api-prod-db --force`, - Long: "Destroy (decommission) a package. This will permanently delete the package and all its resources.", - Args: cobra.ExactArgs(1), - RunE: runPkgDestroy, - } - pkgDestroyCmd.Flags().BoolP("force", "f", false, "Skip confirmation prompt") - - pkgResetCmd := &cobra.Command{ - Use: `reset --`, - Short: "Reset package status to 'Initialized'", - Example: `mass package reset api-prod-db`, - Long: helpdocs.MustRender("package/reset"), - Args: cobra.ExactArgs(1), - RunE: runPkgReset, - } - pkgResetCmd.Flags().BoolP("force", "f", false, "Skip confirmation prompt") - - pkgListCmd := &cobra.Command{ - Use: `list -`, - Short: "List packages in an environment", - Aliases: []string{"ls"}, - Example: `mass package list ecomm-prod`, - Long: helpdocs.MustRender("package/list"), - Args: cobra.ExactArgs(1), - RunE: runPkgList, - } - - pkgCmd.AddCommand(pkgConfigureCmd) - pkgCmd.AddCommand(pkgDeployCmd) - pkgCmd.AddCommand(pkgExportCmd) - pkgCmd.AddCommand(pkgGetCmd) - pkgCmd.AddCommand(pkgListCmd) - pkgCmd.AddCommand(pkgPatchCmd) - pkgCmd.AddCommand(pkgCreateCmd) - pkgCmd.AddCommand(pkgVersionCmd) - pkgCmd.AddCommand(pkgDestroyCmd) - pkgCmd.AddCommand(pkgResetCmd) - - return pkgCmd -} - -func runPkgGet(cmd *cobra.Command, args []string) error { - ctx := context.Background() - - outputFormat, err := cmd.Flags().GetString("output") - if err != nil { - return err - } - - pkgID := args[0] - - mdClient, mdClientErr := client.New() - if mdClientErr != nil { - return fmt.Errorf("error initializing massdriver client: %w", mdClientErr) - } - - pkg, err := api.GetPackage(ctx, mdClient, pkgID) - if err != nil { - return err - } - - switch outputFormat { - case "json": - jsonBytes, marshalErr := json.MarshalIndent(pkg, "", " ") - if marshalErr != nil { - return fmt.Errorf("failed to marshal package to JSON: %w", marshalErr) - } - fmt.Println(string(jsonBytes)) - case "text": - err = renderPackage(pkg) - if err != nil { - return err - } - default: - return fmt.Errorf("unsupported output format: %s", outputFormat) - } - - return nil -} - -func renderPackage(pkg *api.Package) error { - tmplBytes, err := packageTemplates.ReadFile("templates/package.get.md.tmpl") - if err != nil { - return fmt.Errorf("failed to read template: %w", err) - } - - funcMap := template.FuncMap{ - "deref": func(s *string) string { - if s == nil { - return "" - } - return *s - }, - } - tmpl, err := template.New("package").Funcs(funcMap).Parse(string(tmplBytes)) - if err != nil { - return fmt.Errorf("failed to parse template: %w", err) - } - - var buf bytes.Buffer - if renderErr := tmpl.Execute(&buf, pkg); renderErr != nil { - return fmt.Errorf("failed to execute template: %w", renderErr) - } - - r, err := glamour.NewTermRenderer(glamour.WithAutoStyle()) - if err != nil { - return err - } - - out, err := r.Render(buf.String()) - if err != nil { - return err - } - - fmt.Print(out) - return nil -} - -func runPkgDeploy(cmd *cobra.Command, args []string) error { - ctx := context.Background() - - name := args[0] - - msg, err := cmd.Flags().GetString("message") - if err != nil { - return err - } - - mdClient, mdClientErr := client.New() - if mdClientErr != nil { - return fmt.Errorf("error initializing massdriver client: %w", mdClientErr) - } - - _, err = pkg.RunDeploy(ctx, mdClient, name, msg) - - return err -} - -func runPkgConfigure(cmd *cobra.Command, args []string) error { - ctx := context.Background() - - packageSlugOrID := args[0] - - params := map[string]any{} - if pkgParamsPath == "-" { - // Read from stdin - if err := json.NewDecoder(os.Stdin).Decode(¶ms); err != nil { - return fmt.Errorf("failed to decode JSON from stdin: %w", err) - } - } else { - if err := files.Read(pkgParamsPath, ¶ms); err != nil { - return err - } - } - - mdClient, mdClientErr := client.New() - if mdClientErr != nil { - return fmt.Errorf("error initializing massdriver client: %w", mdClientErr) - } - - configuredPkg, err := pkg.RunConfigure(ctx, mdClient, packageSlugOrID, params) - if err != nil { - return err - } - - fmt.Printf("✅ Package `%s` configured successfully\n", configuredPkg.Slug) - - // Get package details to build URL - pkgDetails, err := api.GetPackage(ctx, mdClient, configuredPkg.Slug) - if err == nil && pkgDetails.Environment != nil && pkgDetails.Environment.Project != nil && pkgDetails.Manifest != nil { - urlHelper, urlErr := api.NewURLHelper(ctx, mdClient) - if urlErr == nil { - fmt.Printf("🔗 %s\n", urlHelper.PackageURL(pkgDetails.Environment.Project.Slug, pkgDetails.Environment.Slug, pkgDetails.Manifest.Slug)) - } - } - - return nil -} - -func runPkgPatch(cmd *cobra.Command, args []string) error { - ctx := context.Background() - - packageSlugOrID := args[0] - - cmd.SilenceUsage = true - - mdClient, mdClientErr := client.New() - if mdClientErr != nil { - return fmt.Errorf("error initializing massdriver client: %w", mdClientErr) - } - - _, err := pkg.RunPatch(ctx, mdClient, packageSlugOrID, pkgPatchQueries) - - var name = lipgloss.NewStyle().SetString(packageSlugOrID).Foreground(lipgloss.Color("#7D56F4")) - msg := fmt.Sprintf("Patching: %s", name) - fmt.Println(msg) - - return err -} - -func runPkgExport(cmd *cobra.Command, args []string) error { - ctx := context.Background() - - packageSlugOrID := args[0] - - cmd.SilenceUsage = true - - mdClient, mdClientErr := client.New() - if mdClientErr != nil { - return fmt.Errorf("error initializing massdriver client: %w", mdClientErr) - } - - exportErr := pkg.RunExport(ctx, mdClient, packageSlugOrID) - if exportErr != nil { - return fmt.Errorf("failed to export package: %w", exportErr) - } - - return nil -} - -func runPkgCreate(cmd *cobra.Command, args []string) error { - ctx := context.Background() - - fullSlug := args[0] - name, err := cmd.Flags().GetString("name") - if err != nil { - return err - } - - bundleIDOrName, err := cmd.Flags().GetString("bundle") - if err != nil { - return err - } - - // Parse project-env-manifest format: extract project (first), env (second), and manifest (third) - // Format is $proj-$env-$manifest where each part has no hyphens - parts := strings.Split(fullSlug, "-") - if len(parts) != 3 { - return fmt.Errorf("unable to determine project, environment, and manifest from slug %s (expected format: project-env-manifest)", fullSlug) - } - projectIDOrSlug := parts[0] - environmentSlug := parts[1] - manifestSlug := parts[2] - - if name == "" { - name = manifestSlug - } - - mdClient, mdClientErr := client.New() - if mdClientErr != nil { - return fmt.Errorf("error initializing massdriver client: %w", mdClientErr) - } - - _, err = api.CreateManifest(ctx, mdClient, bundleIDOrName, projectIDOrSlug, name, manifestSlug, "") - if err != nil { - return err - } - - fmt.Printf("✅ Package `%s` created successfully\n", fullSlug) - urlHelper, urlErr := api.NewURLHelper(ctx, mdClient) - if urlErr == nil { - fmt.Printf("🔗 %s\n", urlHelper.PackageURL(projectIDOrSlug, environmentSlug, manifestSlug)) - } - return nil -} - -func runPkgVersion(cmd *cobra.Command, args []string) error { - ctx := context.Background() - - packageIDAndVersion := args[0] - - // Parse package-id@version format - parts := strings.Split(packageIDAndVersion, "@") - if len(parts) != 2 { - return fmt.Errorf("invalid format: expected @, got %s", packageIDAndVersion) - } - packageID := parts[0] - version := parts[1] - - releaseChannel, err := cmd.Flags().GetString("release-channel") - if err != nil { - return err - } - - // Convert release channel to ReleaseStrategy enum value - var releaseStrategy api.ReleaseStrategy - switch releaseChannel { - case "development": - releaseStrategy = api.ReleaseStrategyDevelopment - case "stable": - releaseStrategy = api.ReleaseStrategyStable - default: - return fmt.Errorf("invalid release-channel: must be 'stable' or 'development', got '%s'", releaseChannel) - } - - mdClient, mdClientErr := client.New() - if mdClientErr != nil { - return fmt.Errorf("error initializing massdriver client: %w", mdClientErr) - } - - updatedPkg, err := api.SetPackageVersion(ctx, mdClient, packageID, version, releaseStrategy) - if err != nil { - return err - } - - fmt.Printf("✅ Package `%s` version set successfully\n", updatedPkg.Slug) - - // Get package details to build URL - pkgDetails, err := api.GetPackage(ctx, mdClient, updatedPkg.Slug) - if err == nil && pkgDetails.Environment != nil && pkgDetails.Environment.Project != nil && pkgDetails.Manifest != nil { - urlHelper, urlErr := api.NewURLHelper(ctx, mdClient) - if urlErr == nil { - fmt.Printf("🔗 %s\n", urlHelper.PackageURL(pkgDetails.Environment.Project.Slug, pkgDetails.Environment.Slug, pkgDetails.Manifest.Slug)) - } - } - - return nil -} - -func runPkgDestroy(cmd *cobra.Command, args []string) error { - ctx := context.Background() - - packageSlugOrID := args[0] - force, err := cmd.Flags().GetBool("force") - if err != nil { - return err - } - - cmd.SilenceUsage = true - - mdClient, mdClientErr := client.New() - if mdClientErr != nil { - return fmt.Errorf("error initializing massdriver client: %w", mdClientErr) - } - - // Get package details for confirmation and URL - pkg, err := api.GetPackage(ctx, mdClient, packageSlugOrID) - if err != nil { - return err - } - - // Prompt for confirmation - requires typing the package slug unless --force is used - if !force { - fmt.Printf("WARNING: This will permanently decommission package `%s` and all its resources.\n", pkg.Slug) - fmt.Printf("Type `%s` to confirm decommission: ", pkg.Slug) - reader := bufio.NewReader(os.Stdin) - answer, _ := reader.ReadString('\n') - answer = strings.TrimSpace(answer) - - if answer != pkg.Slug { - fmt.Println("Decommission cancelled.") - return nil - } - } - - _, err = api.DecommissionPackage(ctx, mdClient, pkg.ID, "") - if err != nil { - return err - } - - fmt.Printf("✅ Package `%s` decommission started\n", pkg.Slug) - - // Get package details to build URL - if pkg.Environment != nil && pkg.Environment.Project != nil && pkg.Manifest != nil { - urlHelper, urlErr := api.NewURLHelper(ctx, mdClient) - if urlErr == nil { - fmt.Printf("🔗 %s\n", urlHelper.PackageURL(pkg.Environment.Project.Slug, pkg.Environment.Slug, pkg.Manifest.Slug)) - } - } - - return nil -} - -func runPkgReset(cmd *cobra.Command, args []string) error { - ctx := context.Background() - - packageSlugOrID := args[0] - - force, err := cmd.Flags().GetBool("force") - if err != nil { - return err - } - - cmd.SilenceUsage = true - - mdClient, mdClientErr := client.New() - if mdClientErr != nil { - return fmt.Errorf("error initializing massdriver client: %w", mdClientErr) - } - - // Get package details for confirmation - pkgDetails, err := api.GetPackage(ctx, mdClient, packageSlugOrID) - if err != nil { - return err - } - - // Prompt for confirmation unless --force is used - if !force { - fmt.Printf("%s: This will reset package `%s` to 'Initialized' state and delete deployment history.\n", prettylogs.Orange("WARNING"), pkgDetails.Slug) - fmt.Printf("Type `%s` to confirm reset: ", pkgDetails.Slug) - reader := bufio.NewReader(os.Stdin) - answer, _ := reader.ReadString('\n') - answer = strings.TrimSpace(answer) - - if answer != pkgDetails.Slug { - fmt.Println("Reset cancelled.") - return nil - } - } - - pkg, err := pkg.RunReset(ctx, mdClient, packageSlugOrID) - if err != nil { - return err - } - - var name = lipgloss.NewStyle().SetString(pkg.Slug).Foreground(lipgloss.Color("#7D56F4")) - msg := fmt.Sprintf("✅ Package %s reset successfully", name) - fmt.Println(msg) - - return nil -} - -func runPkgList(cmd *cobra.Command, args []string) error { - ctx := context.Background() - - environmentSlug := args[0] - - cmd.SilenceUsage = true - - mdClient, mdClientErr := client.New() - if mdClientErr != nil { - return fmt.Errorf("error initializing massdriver client: %w", mdClientErr) - } - - env, err := api.GetEnvironment(ctx, mdClient, environmentSlug) - if err != nil { - return err - } - - tbl := cli.NewTable("ID", "Name", "Bundle", "Status") - - for _, p := range env.Packages { - name := "" - if p.Manifest != nil { - name = p.Manifest.Name - } - bundleName := "" - if p.Bundle != nil { - bundleName = p.Bundle.Name - } - tbl.AddRow(p.Slug, name, bundleName, p.Status) - } - - tbl.Print() - - return nil -} diff --git a/cmd/preview.go b/cmd/preview.go index e1121572..0bf58bee 100644 --- a/cmd/preview.go +++ b/cmd/preview.go @@ -7,7 +7,7 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/massdriver-cloud/mass/docs/helpdocs" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/commands/preview" "github.com/massdriver-cloud/mass/internal/files" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" diff --git a/cmd/project.go b/cmd/project.go index c2983c23..a02cea54 100644 --- a/cmd/project.go +++ b/cmd/project.go @@ -13,7 +13,8 @@ import ( "github.com/charmbracelet/glamour" "github.com/massdriver-cloud/mass/docs/helpdocs" - "github.com/massdriver-cloud/mass/internal/api" + apiv0 "github.com/massdriver-cloud/mass/internal/api/v0" + "github.com/massdriver-cloud/mass/internal/api/v1" "github.com/massdriver-cloud/mass/internal/cli" "github.com/massdriver-cloud/mass/internal/commands/project" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" @@ -86,7 +87,7 @@ func NewCmdProject() *cobra.Command { func runProjectGet(cmd *cobra.Command, args []string) error { ctx := context.Background() - projectSlug := args[0] + projectID := args[0] outputFormat, err := cmd.Flags().GetString("output") if err != nil { return err @@ -97,23 +98,11 @@ func runProjectGet(cmd *cobra.Command, args []string) error { return fmt.Errorf("error initializing massdriver client: %w", mdClientErr) } - projects, err := api.ListProjects(ctx, mdClient) + project, err := api.GetProject(ctx, mdClient, projectID) if err != nil { return err } - var project *api.Project - for _, p := range projects { - if p.Slug == projectSlug { - project = &p - break - } - } - - if project == nil { - return fmt.Errorf("project not found: %s", projectSlug) - } - switch outputFormat { case "json": jsonBytes, marshalErr := json.MarshalIndent(project, "", " ") @@ -166,14 +155,14 @@ func runProjectList(cmd *cobra.Command, args []string) error { for _, project := range projects { monthly := "" daily := "" - if project.Cost.Monthly.Average.Amount != nil { - monthly = fmt.Sprintf("%v", *project.Cost.Monthly.Average.Amount) + if project.Cost.MonthlyAverage.Amount != nil { + monthly = fmt.Sprintf("%v", *project.Cost.MonthlyAverage.Amount) } - if project.Cost.Daily.Average.Amount != nil { - daily = fmt.Sprintf("%v", *project.Cost.Daily.Average.Amount) + if project.Cost.DailyAverage.Amount != nil { + daily = fmt.Sprintf("%v", *project.Cost.DailyAverage.Amount) } description := cli.TruncateString(project.Description, 60) - tbl.AddRow(project.Slug, project.Name, description, monthly, daily) + tbl.AddRow(project.ID, project.Name, description, monthly, daily) } tbl.Print() @@ -214,13 +203,13 @@ func renderProject(project *api.Project) error { func runProjectCreate(cmd *cobra.Command, args []string) error { ctx := context.Background() - slug := args[0] + id := args[0] name, err := cmd.Flags().GetString("name") if err != nil { return err } if name == "" { - name = slug + name = id } mdClient, mdClientErr := client.New() @@ -228,15 +217,21 @@ func runProjectCreate(cmd *cobra.Command, args []string) error { return fmt.Errorf("error initializing massdriver client: %w", mdClientErr) } - project, err := api.CreateProject(ctx, mdClient, name, slug, "") + input := api.CreateProjectInput{ + Id: id, + Name: name, + Description: "", + } + + project, err := api.CreateProject(ctx, mdClient, input) if err != nil { return err } - fmt.Printf("✅ Project `%s` created successfully\n", project.Slug) - urlHelper, urlErr := api.NewURLHelper(ctx, mdClient) + fmt.Printf("✅ Project `%s` created successfully\n", project.ID) + urlHelper, urlErr := apiv0.NewURLHelper(ctx, mdClient) if urlErr == nil { - fmt.Printf("🔗 %s\n", urlHelper.ProjectURL(project.Slug)) + fmt.Printf("🔗 %s\n", urlHelper.ProjectURL(project.ID)) } return nil } @@ -244,7 +239,7 @@ func runProjectCreate(cmd *cobra.Command, args []string) error { func runProjectDelete(cmd *cobra.Command, args []string) error { ctx := context.Background() - projectIDOrSlug := args[0] + projectID := args[0] force, err := cmd.Flags().GetBool("force") if err != nil { return err @@ -258,30 +253,30 @@ func runProjectDelete(cmd *cobra.Command, args []string) error { } // Get project details for confirmation - project, getErr := api.GetProject(ctx, mdClient, projectIDOrSlug) + project, getErr := api.GetProject(ctx, mdClient, projectID) if getErr != nil { return fmt.Errorf("error getting project: %w", getErr) } - // Prompt for confirmation - requires typing the project slug unless --force is used + // Prompt for confirmation - requires typing the project ID unless --force is used if !force { - fmt.Printf("WARNING: This will permanently delete project `%s` and all its resources.\n", project.Slug) - fmt.Printf("Type `%s` to confirm deletion: ", project.Slug) + fmt.Printf("WARNING: This will permanently delete project `%s` and all its resources.\n", project.ID) + fmt.Printf("Type `%s` to confirm deletion: ", project.ID) reader := bufio.NewReader(os.Stdin) answer, _ := reader.ReadString('\n') answer = strings.TrimSpace(answer) - if answer != project.Slug { + if answer != project.ID { fmt.Println("Deletion cancelled.") return nil } } - deletedProject, err := api.DeleteProject(ctx, mdClient, projectIDOrSlug) + deletedProject, err := api.DeleteProject(ctx, mdClient, projectID) if err != nil { return err } - fmt.Printf("Project %s deleted successfully (ID: %s)\n", deletedProject.Slug, deletedProject.ID) + fmt.Printf("Project %s deleted successfully (ID: %s)\n", deletedProject.Name, deletedProject.ID) return nil } diff --git a/cmd/root.go b/cmd/root.go index f9d002e9..8f83855c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -48,7 +48,7 @@ func Execute() { rootCmd.AddCommand(NewCmdSchema()) rootCmd.AddCommand(NewCmdServer()) rootCmd.AddCommand(NewCmdVersion()) - rootCmd.AddCommand(NewCmdPkg()) + rootCmd.AddCommand(NewCmdInstance()) if err := rootCmd.Execute(); err != nil { os.Exit(1) } diff --git a/cmd/templates/environment.get.md.tmpl b/cmd/templates/environment.get.md.tmpl index 85acd62a..c22ce62e 100644 --- a/cmd/templates/environment.get.md.tmpl +++ b/cmd/templates/environment.get.md.tmpl @@ -1,17 +1,17 @@ # Environment: {{.Name}} -**Slug:** {{.Slug}} +**ID:** {{.ID}} **Description:** {{.Description}} -## Packages +## Instances | Name | Bundle | Status | | ---- | ------ | ------ | -{{- range .Packages}} -| **{{.Manifest.Name}}** | {{.Bundle.Name}} | {{.Status}} | +{{- range .Blueprint.Instances}} +| **{{.Name}}** | {{.Bundle.Name}} | {{.Status}} | {{- end}} ## Cost -**Monthly Average:** {{with .Cost.Monthly.Average.Amount}}${{.}}{{end}} +**Monthly Average:** {{with .Cost.MonthlyAverage.Amount}}${{.}}{{end}} -**Daily Average:** {{with .Cost.Daily.Average.Amount}}${{.}}{{end}} +**Daily Average:** {{with .Cost.DailyAverage.Amount}}${{.}}{{end}} diff --git a/cmd/templates/package.get.md.tmpl b/cmd/templates/instance.get.md.tmpl similarity index 100% rename from cmd/templates/package.get.md.tmpl rename to cmd/templates/instance.get.md.tmpl diff --git a/cmd/templates/project.get.md.tmpl b/cmd/templates/project.get.md.tmpl index 4e7eb7d6..30bb605f 100644 --- a/cmd/templates/project.get.md.tmpl +++ b/cmd/templates/project.get.md.tmpl @@ -1,15 +1,15 @@ # Project: {{.Name}} -**Slug:** {{.Slug}} +**ID:** {{.ID}} **Description:** {{.Description}} ## Environments {{range .Environments}} -- **{{.Name}}** ({{.Slug}}) +- **{{.Name}}** ({{.ID}}) {{end}} ## Cost -**Monthly Average:** {{with .Cost.Monthly.Average.Amount}}${{.}}{{end}} +**Monthly Average:** {{with .Cost.MonthlyAverage.Amount}}${{.}}{{end}} -**Daily Average:** {{with .Cost.Daily.Average.Amount}}${{.}}{{end}} +**Daily Average:** {{with .Cost.DailyAverage.Amount}}${{.}}{{end}} diff --git a/cmd/version.go b/cmd/version.go index af51430c..7366f840 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/prettylogs" "github.com/massdriver-cloud/mass/internal/version" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" diff --git a/docs/generated/mass.md b/docs/generated/mass.md index 5be5553e..2c334ba6 100644 --- a/docs/generated/mass.md +++ b/docs/generated/mass.md @@ -40,8 +40,8 @@ Configure and deploying infrastructure and applications. * [mass docs](/cli/commands/mass_docs) - Gen docs * [mass environment](/cli/commands/mass_environment) - Environment management * [mass image](/cli/commands/mass_image) - Container image integration Massdriver +* [mass instance](/cli/commands/mass_instance) - Manage instances of IaC deployed in environments. * [mass logs](/cli/commands/mass_logs) - Get deployment logs -* [mass package](/cli/commands/mass_package) - Manage packages of IaC deployed in environments. * [mass preview](/cli/commands/mass_preview) - Create & deploy preview environments * [mass project](/cli/commands/mass_project) - Project management * [mass schema](/cli/commands/mass_schema) - Manage JSON Schemas diff --git a/docs/generated/mass_instance.md b/docs/generated/mass_instance.md new file mode 100644 index 00000000..b7f8c454 --- /dev/null +++ b/docs/generated/mass_instance.md @@ -0,0 +1,42 @@ +--- +id: mass_instance.md +slug: /cli/commands/mass_instance +title: Mass Instance +sidebar_label: Mass Instance +--- +## mass instance + +Manage instances of IaC deployed in environments. + +### Synopsis + +# Packages + +[Packages](https://docs.massdriver.cloud/concepts/packages) are instances of infrastructure-as-code modules on your environment canvas. + +Packages are used to: +- Deploy infrastructure components +- Configure application services +- Manage environment-specific settings +- Connect different components together + + +### Options + +``` + -h, --help help for instance +``` + +### SEE ALSO + +* [mass](/cli/commands/mass) - Massdriver Cloud CLI +* [mass instance configure](/cli/commands/mass_instance_configure) - Configure instance +* [mass instance create](/cli/commands/mass_instance_create) - Create a manifest (add bundle to project) +* [mass instance deploy](/cli/commands/mass_instance_deploy) - Deploy instances +* [mass instance destroy](/cli/commands/mass_instance_destroy) - Destroy (decommission) an instance +* [mass instance export](/cli/commands/mass_instance_export) - Export instances +* [mass instance get](/cli/commands/mass_instance_get) - Get an instance +* [mass instance list](/cli/commands/mass_instance_list) - List instances in an environment +* [mass instance patch](/cli/commands/mass_instance_patch) - Patch individual instance parameter values +* [mass instance reset](/cli/commands/mass_instance_reset) - Reset instance status to 'Initialized' +* [mass instance version](/cli/commands/mass_instance_version) - Set instance version diff --git a/docs/generated/mass_instance_configure.md b/docs/generated/mass_instance_configure.md new file mode 100644 index 00000000..b6efe874 --- /dev/null +++ b/docs/generated/mass_instance_configure.md @@ -0,0 +1,76 @@ +--- +id: mass_instance_configure.md +slug: /cli/commands/mass_instance_configure +title: Mass Instance Configure +sidebar_label: Mass Instance Configure +--- +## mass instance configure + +Configure instance + +### Synopsis + +# Configure infrastructure on Massdriver. + +Your IaC must be published as a [bundle](https://docs.massdriver.cloud/bundles) to Massdriver first and be added to an environment's canvas. + +This command will replace the full configuration of an infrastructure package in Massdriver. + +## Examples + +You can configure the package using the `slug` identifier. + +The `slug` can be found by hovering over the bundle in the Massdriver diagram. The package slug is a combination of the `--` + +_Note:_ Parameter files support bash interpolation. + +Configure from file: + +```shell +mass package configure ecomm-prod-vpc --params=params.json +mass package configure ecomm-prod-vpc --params=params.tfvars +mass package configure ecomm-prod-vpc --params=params.yaml +mass package configure ecomm-prod-vpc --params=params.toml +``` + +Configure from STDIN: + +```shell +echo '{"hello": "world"}' | mass package configure ecomm-prod-vpc --params=- +``` + +Copy configuration between environments: + +```shell +mass pkg get api-prod-web -o json | jq .params | mass pkg cfg api-staging-web --params - +``` + +Copy configuration and change some values: +```shell +mass pkg get api-prod-web -o json \ + | jq '.params.domain = "staging.example.com"' \ + | jq '.params.image.tag = "latest"' \ + | mass pkg cfg api-staging-web --params - +``` + + +``` +mass instance configure -- [flags] +``` + +### Examples + +``` +mass instance configure ecomm-prod-vpc --params=params.json +``` + +### Options + +``` + -h, --help help for configure + -p, --params string Path to params json, tfvars or yaml file. Use '-' to read from stdin. This file supports bash interpolation. (default "./params.json") +``` + +### SEE ALSO + +* [mass instance](/cli/commands/mass_instance) - Manage instances of IaC deployed in environments. diff --git a/docs/generated/mass_instance_create.md b/docs/generated/mass_instance_create.md new file mode 100644 index 00000000..70f55d37 --- /dev/null +++ b/docs/generated/mass_instance_create.md @@ -0,0 +1,67 @@ +--- +id: mass_instance_create.md +slug: /cli/commands/mass_instance_create +title: Mass Instance Create +sidebar_label: Mass Instance Create +--- +## mass instance create + +Create a manifest (add bundle to project) + +### Synopsis + +# Create Manifest + +Adds a bundle to a project as a manifest. A manifest is the context of how you plan to use a bundle in your project (e.g., a Redis bundle used for "page caching" vs "user sessions" would be two different manifests). + +Manifests are added to projects and automatically created in all environments. When you configure a package (the deployed instance of a manifest), it's configured per environment. + +## Usage + +```bash +mass package create [flags] +``` + +The slug format is `project-env-manifest`, where: +- `project`: The project slug (first segment, no hyphens) +- `env`: The environment slug (second segment, no hyphens) +- `manifest`: The manifest slug (third segment, no hyphens) + +## Flags + +- `--name, -n`: Manifest name (defaults to manifest slug if not provided) +- `--bundle, -b`: Bundle ID or name (required) + +## Examples + +```bash +# Create a manifest "table" in project "test1" using bundle "aws-collab-dynamodb" +# The slug format is "test1-qa-table" where "test1" is the project, "qa" is the env, and "table" is the manifest +mass package create test1-qa-table --bundle aws-collab-dynamodb + +# Create a manifest with a custom name +mass package create test1-qa-table --name "Database Table" --bundle aws-collab-dynamodb +``` + + +``` +mass instance create [slug] [flags] +``` + +### Examples + +``` +mass instance create dbbundle-test-serverless --bundle aws-rds-cluster +``` + +### Options + +``` + -b, --bundle string Bundle ID or name (required) + -h, --help help for create + -n, --name string Manifest name (defaults to slug if not provided) +``` + +### SEE ALSO + +* [mass instance](/cli/commands/mass_instance) - Manage instances of IaC deployed in environments. diff --git a/docs/generated/mass_instance_deploy.md b/docs/generated/mass_instance_deploy.md new file mode 100644 index 00000000..04866acd --- /dev/null +++ b/docs/generated/mass_instance_deploy.md @@ -0,0 +1,47 @@ +--- +id: mass_instance_deploy.md +slug: /cli/commands/mass_instance_deploy +title: Mass Instance Deploy +sidebar_label: Mass Instance Deploy +--- +## mass instance deploy + +Deploy instances + +### Synopsis + +# Deploy packages on Massdriver. + +Your IaC must be published as a [bundle](https://docs.massdriver.cloud/bundles) to Massdriver first and be added to an environment's canvas. + +## Examples + +You can deploy the package using the `slug` identifier. + +The `slug` can be found by hovering over the bundle in the Massdriver diagram. The package slug is a combination of the `--` + +```shell +mass package deploy ecomm-prod-vpc +``` + + +``` +mass instance deploy -- [flags] +``` + +### Examples + +``` +mass instance deploy ecomm-prod-vpc +``` + +### Options + +``` + -h, --help help for deploy + -m, --message string Add a message when deploying +``` + +### SEE ALSO + +* [mass instance](/cli/commands/mass_instance) - Manage instances of IaC deployed in environments. diff --git a/docs/generated/mass_instance_destroy.md b/docs/generated/mass_instance_destroy.md new file mode 100644 index 00000000..95142fa0 --- /dev/null +++ b/docs/generated/mass_instance_destroy.md @@ -0,0 +1,34 @@ +--- +id: mass_instance_destroy.md +slug: /cli/commands/mass_instance_destroy +title: Mass Instance Destroy +sidebar_label: Mass Instance Destroy +--- +## mass instance destroy + +Destroy (decommission) an instance + +### Synopsis + +Destroy (decommission) an instance. This will permanently delete the instance and all its resources. + +``` +mass instance destroy -- [flags] +``` + +### Examples + +``` +mass instance destroy api-prod-db --force +``` + +### Options + +``` + -f, --force Skip confirmation prompt + -h, --help help for destroy +``` + +### SEE ALSO + +* [mass instance](/cli/commands/mass_instance) - Manage instances of IaC deployed in environments. diff --git a/docs/generated/mass_instance_export.md b/docs/generated/mass_instance_export.md new file mode 100644 index 00000000..e70db5e6 --- /dev/null +++ b/docs/generated/mass_instance_export.md @@ -0,0 +1,68 @@ +--- +id: mass_instance_export.md +slug: /cli/commands/mass_instance_export +title: Mass Instance Export +sidebar_label: Mass Instance Export +--- +## mass instance export + +Export instances + +### Synopsis + +# Export Package Details + +Exports a package to the local filesystem. This is useful for backups and migrations. + +Data will be exported into a directory, named via the package slug: + +```bash +package +├── artifact_.json +├── bundle +│   ├── +├── params.json +├── .tfstate.json +``` + +The data which will be exported for each package includes: +- **`artifact_.json`**: Each artifact for the deploy package (if applicable) +- **`bundle`**: Directory containing deployed bundle version +- **`params.json`**: Current package configuration +- **`.tfstate.json`**: Terraform/OpenTofu state file for each step (if applicable) + +Data will only be exported for packages in the **`RUNNING`** state. Data will NOT be exported for packages in the **`INITIALIZED`**, **`DECOMMISSIONED`** or **`FAILED`** state. Packages which are remote references will only download the artifacts files. + +## Usage + +```bash +mass package export -- +``` + +## Examples + +```bash +# Export the "app" package in the "prod" environment of the "web" project +mass package export web-prod-app +``` + + +``` +mass instance export -- [flags] +``` + +### Examples + +``` +mass instance export ecomm-prod-vpc +``` + +### Options + +``` + -h, --help help for export +``` + +### SEE ALSO + +* [mass instance](/cli/commands/mass_instance) - Manage instances of IaC deployed in environments. diff --git a/docs/generated/mass_instance_get.md b/docs/generated/mass_instance_get.md new file mode 100644 index 00000000..8a12c02e --- /dev/null +++ b/docs/generated/mass_instance_get.md @@ -0,0 +1,54 @@ +--- +id: mass_instance_get.md +slug: /cli/commands/mass_instance_get +title: Mass Instance Get +sidebar_label: Mass Instance Get +--- +## mass instance get + +Get an instance + +### Synopsis + +# Get Package + +Retrieves detailed information about a specific package from Massdriver. + +Your infrastructure must be published as a [bundle](https://docs.massdriver.cloud/bundles) to Massdriver first and be added to an environment's canvas. + +## Usage + +```bash +mass package get +``` + +## Examples + +```bash +# Get details for a VPC package in the ecommerce production environment +mass package get ecomm-prod-vpc +``` + +The package slug can be found by hovering over the bundle in the Massdriver diagram. It follows the format: `--`. + + +``` +mass instance get -- [flags] +``` + +### Examples + +``` +mass instance get ecomm-prod-vpc +``` + +### Options + +``` + -h, --help help for get + -o, --output string Output format (text or json) (default "text") +``` + +### SEE ALSO + +* [mass instance](/cli/commands/mass_instance) - Manage instances of IaC deployed in environments. diff --git a/docs/generated/mass_instance_list.md b/docs/generated/mass_instance_list.md new file mode 100644 index 00000000..d28fd470 --- /dev/null +++ b/docs/generated/mass_instance_list.md @@ -0,0 +1,49 @@ +--- +id: mass_instance_list.md +slug: /cli/commands/mass_instance_list +title: Mass Instance List +sidebar_label: Mass Instance List +--- +## mass instance list + +List instances in an environment + +### Synopsis + +# List Packages + +Lists all packages in a Massdriver environment. + +## Usage + +```bash +mass package list - +``` + +## Examples + +```bash +# List all packages in the "ecomm" project's "prod" environment +mass package list ecomm-prod +``` + + +``` +mass instance list - [flags] +``` + +### Examples + +``` +mass instance list ecomm-prod +``` + +### Options + +``` + -h, --help help for list +``` + +### SEE ALSO + +* [mass instance](/cli/commands/mass_instance) - Manage instances of IaC deployed in environments. diff --git a/docs/generated/mass_instance_patch.md b/docs/generated/mass_instance_patch.md new file mode 100644 index 00000000..a3cd19ab --- /dev/null +++ b/docs/generated/mass_instance_patch.md @@ -0,0 +1,53 @@ +--- +id: mass_instance_patch.md +slug: /cli/commands/mass_instance_patch +title: Mass Instance Patch +sidebar_label: Mass Instance Patch +--- +## mass instance patch + +Patch individual instance parameter values + +### Synopsis + +# Patching package configuration on Massdriver. + +Your IaC must be published as a [bundle](https://docs.massdriver.cloud/bundles) to Massdriver first and be added to an environment's canvas. + +Patching will perform a client-side patch of fields set on `--set`. + +The `--set` argument can be called multiple times to set multiple values. + +`--set` expects a JQ expression to set values. + +## Examples + +You can patch the package using the `slug` identifier. + +The `slug` can be found by hovering over the bundle in the Massdriver diagram. The package slug is a combination of the `--` + +```shell +mass package patch ecomm-prod-db --set='.version = "13.4"' +``` + + +``` +mass instance patch -- [flags] +``` + +### Examples + +``` +mass instance patch ecomm-prod-db --set='.version = "13.4"' +``` + +### Options + +``` + -h, --help help for patch + -s, --set stringArray Sets an instance parameter value using JQ expressions. +``` + +### SEE ALSO + +* [mass instance](/cli/commands/mass_instance) - Manage instances of IaC deployed in environments. diff --git a/docs/generated/mass_instance_reset.md b/docs/generated/mass_instance_reset.md new file mode 100644 index 00000000..2cd468b9 --- /dev/null +++ b/docs/generated/mass_instance_reset.md @@ -0,0 +1,49 @@ +--- +id: mass_instance_reset.md +slug: /cli/commands/mass_instance_reset +title: Mass Instance Reset +sidebar_label: Mass Instance Reset +--- +## mass instance reset + +Reset instance status to 'Initialized' + +### Synopsis + +# Reset Package Status + +This command allows you to reset a package status back to 'Initialized'. This should only be used when a package is in an unrecoverable state - common situations include a package stuck in 'Pending' due to deployment issues, or a package that cannot be successfully decommissioned due to deployment failures. + +## Examples + +You can reset the package using the `slug` identifier. + +The `slug` can be found by hovering over the bundle in the Massdriver diagram. The package slug is a combination of the `--` + +Reset and delete the deployment history: + +```shell +mass package reset ecomm-prod-vpc +``` + + +``` +mass instance reset -- [flags] +``` + +### Examples + +``` +mass instance reset api-prod-db +``` + +### Options + +``` + -f, --force Skip confirmation prompt + -h, --help help for reset +``` + +### SEE ALSO + +* [mass instance](/cli/commands/mass_instance) - Manage instances of IaC deployed in environments. diff --git a/docs/generated/mass_instance_version.md b/docs/generated/mass_instance_version.md new file mode 100644 index 00000000..cb54c7d0 --- /dev/null +++ b/docs/generated/mass_instance_version.md @@ -0,0 +1,65 @@ +--- +id: mass_instance_version.md +slug: /cli/commands/mass_instance_version +title: Mass Instance Version +sidebar_label: Mass Instance Version +--- +## mass instance version + +Set instance version + +### Synopsis + +# Set package version + +Set the version or release channel for a package in Massdriver. + +## Examples + +Set the version for a package using the `slug@version` format: + +```shell +mass package version api-prod-db@latest +``` + +Set the version with a specific release channel: + +```shell +mass package version api-prod-db@latest --release-channel development +``` + +The `slug` can be found in the package info panel. The package slug is a combination of the `--`. + +## Release Channels + +- `stable` (default): Package receives only stable releases +- `development`: Package receives both stable and development releases + +## Version Format + +The version can be: +- A semantic version (e.g., `1.2.3`) +- A version constraint (e.g., `~1.2`, `~1`) +- A release channel (e.g., `latest`) + + +``` +mass instance version @ [flags] +``` + +### Examples + +``` +mass instance version api-prod-db@latest --release-channel development +``` + +### Options + +``` + -h, --help help for version + --release-channel string Release strategy (stable or development) (default "stable") +``` + +### SEE ALSO + +* [mass instance](/cli/commands/mass_instance) - Manage instances of IaC deployed in environments. diff --git a/docs/helpdocs/package.md b/docs/helpdocs/instance.md similarity index 100% rename from docs/helpdocs/package.md rename to docs/helpdocs/instance.md diff --git a/docs/helpdocs/package/configure.md b/docs/helpdocs/instance/configure.md similarity index 100% rename from docs/helpdocs/package/configure.md rename to docs/helpdocs/instance/configure.md diff --git a/docs/helpdocs/package/create.md b/docs/helpdocs/instance/create.md similarity index 100% rename from docs/helpdocs/package/create.md rename to docs/helpdocs/instance/create.md diff --git a/docs/helpdocs/package/deploy.md b/docs/helpdocs/instance/deploy.md similarity index 100% rename from docs/helpdocs/package/deploy.md rename to docs/helpdocs/instance/deploy.md diff --git a/docs/helpdocs/package/export.md b/docs/helpdocs/instance/export.md similarity index 100% rename from docs/helpdocs/package/export.md rename to docs/helpdocs/instance/export.md diff --git a/docs/helpdocs/package/get.md b/docs/helpdocs/instance/get.md similarity index 100% rename from docs/helpdocs/package/get.md rename to docs/helpdocs/instance/get.md diff --git a/docs/helpdocs/package/list.md b/docs/helpdocs/instance/list.md similarity index 100% rename from docs/helpdocs/package/list.md rename to docs/helpdocs/instance/list.md diff --git a/docs/helpdocs/package/patch.md b/docs/helpdocs/instance/patch.md similarity index 100% rename from docs/helpdocs/package/patch.md rename to docs/helpdocs/instance/patch.md diff --git a/docs/helpdocs/package/reset.md b/docs/helpdocs/instance/reset.md similarity index 100% rename from docs/helpdocs/package/reset.md rename to docs/helpdocs/instance/reset.md diff --git a/docs/helpdocs/package/version.md b/docs/helpdocs/instance/version.md similarity index 100% rename from docs/helpdocs/package/version.md rename to docs/helpdocs/instance/version.md diff --git a/go.mod b/go.mod index 02f7b8d5..6e3a330d 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/massdriver-cloud/mass -go 1.24.2 +go 1.24 require ( github.com/AlecAivazis/survey/v2 v2.3.7 @@ -19,7 +19,7 @@ require ( github.com/itchyny/gojq v0.12.16 github.com/manifoldco/promptui v0.9.0 github.com/massdriver-cloud/airlock v0.0.9 - github.com/massdriver-cloud/massdriver-sdk-go v0.0.10 + github.com/massdriver-cloud/massdriver-sdk-go v0.1.0 github.com/mattn/go-runewidth v0.0.16 github.com/mitchellh/mapstructure v1.5.0 github.com/moby/moby v27.3.1+incompatible @@ -50,13 +50,17 @@ require ( github.com/KyleBanks/depth v1.2.1 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/agext/levenshtein v1.2.3 // indirect + github.com/agnivade/levenshtein v1.1.1 // indirect github.com/alecthomas/chroma/v2 v2.14.0 // indirect + github.com/alexflint/go-arg v1.4.2 // indirect + github.com/alexflint/go-scalar v1.0.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect + github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect github.com/charmbracelet/x/ansi v0.5.2 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect diff --git a/go.sum b/go.sum index 30134078..7f3b579e 100644 --- a/go.sum +++ b/go.sum @@ -18,18 +18,26 @@ github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63n github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= +github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE= github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E= github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/alexflint/go-arg v1.4.2 h1:lDWZAXxpAnZUq4qwb86p/3rIJJ2Li81EoMbTMujhVa0= +github.com/alexflint/go-arg v1.4.2/go.mod h1:9iRbDxne7LcR/GSvEr7ma++GLpdIU1zrghf2y2768kM= +github.com/alexflint/go-scalar v1.0.0 h1:NGupf1XV/Xb04wXskDFzS0KWOLH632W/EO4fAFi+A70= +github.com/alexflint/go-scalar v1.0.0/go.mod h1:GpHzbCOZXEKMEcygYQ5n/aa4Aq84zbxjy3MxYW0gjYw= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= @@ -40,6 +48,10 @@ github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuP github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= +github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= +github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bradleyjkemp/cupaloy/v2 v2.6.0 h1:knToPYa2xtfg42U3I6punFEjaGFKWQRXJwj0JTv4mTs= +github.com/bradleyjkemp/cupaloy/v2 v2.6.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= @@ -80,6 +92,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= +github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= @@ -126,6 +140,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -167,8 +183,14 @@ github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYt github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/massdriver-cloud/airlock v0.0.9 h1:t+jTY6nZEiPZNTKx0wEgQTPztIxL4u0RFvVWXn2/RMc= github.com/massdriver-cloud/airlock v0.0.9/go.mod h1:igJm33JvINiUtbyEspUeKUWyWewG+jYyxO1UDHqLp9Q= -github.com/massdriver-cloud/massdriver-sdk-go v0.0.10 h1:BdydI/YFudQuwyF8zV1BzxgFrFSdpJwXUxVn+/pgUPo= -github.com/massdriver-cloud/massdriver-sdk-go v0.0.10/go.mod h1:6bXMPQmfeKHD3Tm/cwnrkqiZaDEgRUUq0+uZckKRsbM= +github.com/massdriver-cloud/massdriver-sdk-go v0.0.11-0.20260324232018-c1ee1267583a h1:0f5tFS5WMVDbUFaDQxlrL3E/xuZLGjQhpnhU49Wssyk= +github.com/massdriver-cloud/massdriver-sdk-go v0.0.11-0.20260324232018-c1ee1267583a/go.mod h1:Cb0WmZVmpG3B+ZoIxO7fCrfA4+J7giNXHDjoMrjD1S0= +github.com/massdriver-cloud/massdriver-sdk-go v0.0.11-0.20260407201505-d965e01bf594 h1:jcwItMVjK3rPNYUsDNyZb6od57SDgCW747L2+YqK7oI= +github.com/massdriver-cloud/massdriver-sdk-go v0.0.11-0.20260407201505-d965e01bf594/go.mod h1:Cb0WmZVmpG3B+ZoIxO7fCrfA4+J7giNXHDjoMrjD1S0= +github.com/massdriver-cloud/massdriver-sdk-go v0.0.11 h1:xxSfZIkxfEKFnRnp0Hg9gPbcgeGLfuaxR3nEx8dUWGg= +github.com/massdriver-cloud/massdriver-sdk-go v0.0.11/go.mod h1:Cb0WmZVmpG3B+ZoIxO7fCrfA4+J7giNXHDjoMrjD1S0= +github.com/massdriver-cloud/massdriver-sdk-go v0.1.0 h1:jXolz9l30zacxFhPmIcYRNj57OM16YpQJxaxOfzx8Vk= +github.com/massdriver-cloud/massdriver-sdk-go v0.1.0/go.mod h1:Cb0WmZVmpG3B+ZoIxO7fCrfA4+J7giNXHDjoMrjD1S0= github.com/massdriver-cloud/terraform-config-inspect v0.0.1 h1:eLtKFRaklHIxcPvUtZmNacl28n4QIHr29pJzw/u/FKU= github.com/massdriver-cloud/terraform-config-inspect v0.0.1/go.mod h1:3AbDpWxIRMdMAg7FDmTJuVBhCGNwdm49cBIOmUHjqRg= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -261,6 +283,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= diff --git a/internal/api/main.go b/internal/api/main.go deleted file mode 100644 index 69858ad3..00000000 --- a/internal/api/main.go +++ /dev/null @@ -1,3 +0,0 @@ -package api - -//go:generate genqlient diff --git a/internal/api/artifact.go b/internal/api/v0/artifact.go similarity index 100% rename from internal/api/artifact.go rename to internal/api/v0/artifact.go diff --git a/internal/api/artifact_definitions.go b/internal/api/v0/artifact_definitions.go similarity index 100% rename from internal/api/artifact_definitions.go rename to internal/api/v0/artifact_definitions.go diff --git a/internal/api/artifact_definitions_test.go b/internal/api/v0/artifact_definitions_test.go similarity index 96% rename from internal/api/artifact_definitions_test.go rename to internal/api/v0/artifact_definitions_test.go index 38497e9b..5e90e98d 100644 --- a/internal/api/artifact_definitions_test.go +++ b/internal/api/v0/artifact_definitions_test.go @@ -3,7 +3,7 @@ package api_test import ( "testing" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/gqlmock" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" diff --git a/internal/api/artifact_test.go b/internal/api/v0/artifact_test.go similarity index 99% rename from internal/api/artifact_test.go rename to internal/api/v0/artifact_test.go index 7a3ca4b0..9228c612 100644 --- a/internal/api/artifact_test.go +++ b/internal/api/v0/artifact_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/gqlmock" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" diff --git a/internal/api/bundle.go b/internal/api/v0/bundle.go similarity index 100% rename from internal/api/bundle.go rename to internal/api/v0/bundle.go diff --git a/internal/api/bundle_test.go b/internal/api/v0/bundle_test.go similarity index 98% rename from internal/api/bundle_test.go rename to internal/api/v0/bundle_test.go index 9156138a..4ba7c027 100644 --- a/internal/api/bundle_test.go +++ b/internal/api/v0/bundle_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/gqlmock" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" ) diff --git a/internal/api/container_repository.go b/internal/api/v0/container_repository.go similarity index 100% rename from internal/api/container_repository.go rename to internal/api/v0/container_repository.go diff --git a/internal/api/container_repository_test.go b/internal/api/v0/container_repository_test.go similarity index 94% rename from internal/api/container_repository_test.go rename to internal/api/v0/container_repository_test.go index 3ef122f3..e7de2a9c 100644 --- a/internal/api/container_repository_test.go +++ b/internal/api/v0/container_repository_test.go @@ -4,7 +4,7 @@ import ( "reflect" "testing" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/gqlmock" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" ) diff --git a/internal/api/cost.go b/internal/api/v0/cost.go similarity index 100% rename from internal/api/cost.go rename to internal/api/v0/cost.go diff --git a/internal/api/credential.go b/internal/api/v0/credential.go similarity index 100% rename from internal/api/credential.go rename to internal/api/v0/credential.go diff --git a/internal/api/credential_test.go b/internal/api/v0/credential_test.go similarity index 97% rename from internal/api/credential_test.go rename to internal/api/v0/credential_test.go index 1c4fa9a1..8f6cb25f 100644 --- a/internal/api/credential_test.go +++ b/internal/api/v0/credential_test.go @@ -3,7 +3,7 @@ package api_test import ( "testing" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/gqlmock" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/config" diff --git a/internal/api/deployment.go b/internal/api/v0/deployment.go similarity index 100% rename from internal/api/deployment.go rename to internal/api/v0/deployment.go diff --git a/internal/api/deployment_test.go b/internal/api/v0/deployment_test.go similarity index 98% rename from internal/api/deployment_test.go rename to internal/api/v0/deployment_test.go index 17a2be53..f54733d2 100644 --- a/internal/api/deployment_test.go +++ b/internal/api/v0/deployment_test.go @@ -3,7 +3,7 @@ package api_test import ( "testing" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/gqlmock" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/config" diff --git a/internal/api/environment.go b/internal/api/v0/environment.go similarity index 100% rename from internal/api/environment.go rename to internal/api/v0/environment.go diff --git a/internal/api/environment_test.go b/internal/api/v0/environment_test.go similarity index 99% rename from internal/api/environment_test.go rename to internal/api/v0/environment_test.go index 126543ec..4dbbd3c1 100644 --- a/internal/api/environment_test.go +++ b/internal/api/v0/environment_test.go @@ -4,7 +4,7 @@ import ( "reflect" "testing" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/gqlmock" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" ) diff --git a/internal/api/error.go b/internal/api/v0/error.go similarity index 100% rename from internal/api/error.go rename to internal/api/v0/error.go diff --git a/internal/api/error_test.go b/internal/api/v0/error_test.go similarity index 87% rename from internal/api/error_test.go rename to internal/api/v0/error_test.go index 351ae99b..6614f42f 100644 --- a/internal/api/error_test.go +++ b/internal/api/v0/error_test.go @@ -3,7 +3,7 @@ package api_test import ( "testing" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" ) func TestNewMutationError(t *testing.T) { diff --git a/internal/api/genqlient.graphql b/internal/api/v0/genqlient.graphql similarity index 100% rename from internal/api/genqlient.graphql rename to internal/api/v0/genqlient.graphql diff --git a/internal/api/genqlient.yaml b/internal/api/v0/genqlient.yaml similarity index 58% rename from internal/api/genqlient.yaml rename to internal/api/v0/genqlient.yaml index 9561047e..8f3465bf 100644 --- a/internal/api/genqlient.yaml +++ b/internal/api/v0/genqlient.yaml @@ -9,8 +9,8 @@ package: api bindings: JSON: type: map[string]any - marshaler: github.com/massdriver-cloud/mass/pkg/api/scalars.MarshalJSON - unmarshaler: github.com/massdriver-cloud/mass/pkg/api/scalars.UnmarshalJSON + marshaler: github.com/massdriver-cloud/mass/internal/api/v0/scalars.MarshalJSON + unmarshaler: github.com/massdriver-cloud/mass/internal/api/v0/scalars.UnmarshalJSON DateTime: type: time.Time VersionConstraint: @@ -18,4 +18,4 @@ bindings: Markdown: type: string Cursor: - type: github.com/massdriver-cloud/mass/pkg/api/scalars.Cursor + type: github.com/massdriver-cloud/mass/internal/api/v0/scalars.Cursor diff --git a/internal/api/v0/main.go b/internal/api/v0/main.go new file mode 100644 index 00000000..583f832b --- /dev/null +++ b/internal/api/v0/main.go @@ -0,0 +1,3 @@ +package api + +//go:generate go run github.com/Khan/genqlient diff --git a/internal/api/manifest.go b/internal/api/v0/manifest.go similarity index 100% rename from internal/api/manifest.go rename to internal/api/v0/manifest.go diff --git a/internal/api/oci.go b/internal/api/v0/oci.go similarity index 100% rename from internal/api/oci.go rename to internal/api/v0/oci.go diff --git a/internal/api/oci_test.go b/internal/api/v0/oci_test.go similarity index 97% rename from internal/api/oci_test.go rename to internal/api/v0/oci_test.go index 3325ce9d..8feedf39 100644 --- a/internal/api/oci_test.go +++ b/internal/api/v0/oci_test.go @@ -3,7 +3,7 @@ package api_test import ( "testing" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/gqlmock" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" diff --git a/internal/api/package.go b/internal/api/v0/package.go similarity index 100% rename from internal/api/package.go rename to internal/api/v0/package.go diff --git a/internal/api/package_test.go b/internal/api/v0/package_test.go similarity index 98% rename from internal/api/package_test.go rename to internal/api/v0/package_test.go index 52ce995d..94c5f19f 100644 --- a/internal/api/package_test.go +++ b/internal/api/v0/package_test.go @@ -4,7 +4,7 @@ import ( "reflect" "testing" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/gqlmock" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" "github.com/stretchr/testify/assert" diff --git a/internal/api/preview_config.go b/internal/api/v0/preview_config.go similarity index 100% rename from internal/api/preview_config.go rename to internal/api/v0/preview_config.go diff --git a/internal/api/preview_config_test.go b/internal/api/v0/preview_config_test.go similarity index 89% rename from internal/api/preview_config_test.go rename to internal/api/v0/preview_config_test.go index a90f9433..e15e6985 100644 --- a/internal/api/preview_config_test.go +++ b/internal/api/v0/preview_config_test.go @@ -4,7 +4,7 @@ import ( "reflect" "testing" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" ) func TestPreviewConfigGetCredentials(t *testing.T) { diff --git a/internal/api/preview_environment.go b/internal/api/v0/preview_environment.go similarity index 100% rename from internal/api/preview_environment.go rename to internal/api/v0/preview_environment.go diff --git a/internal/api/preview_environment_test.go b/internal/api/v0/preview_environment_test.go similarity index 98% rename from internal/api/preview_environment_test.go rename to internal/api/v0/preview_environment_test.go index 1edf605e..966a3f4f 100644 --- a/internal/api/preview_environment_test.go +++ b/internal/api/v0/preview_environment_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/gqlmock" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" ) diff --git a/internal/api/project.go b/internal/api/v0/project.go similarity index 100% rename from internal/api/project.go rename to internal/api/v0/project.go diff --git a/internal/api/project_test.go b/internal/api/v0/project_test.go similarity index 96% rename from internal/api/project_test.go rename to internal/api/v0/project_test.go index 76442796..775fa69b 100644 --- a/internal/api/project_test.go +++ b/internal/api/v0/project_test.go @@ -3,7 +3,7 @@ package api_test import ( "testing" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/gqlmock" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" ) diff --git a/internal/api/repo.go b/internal/api/v0/repo.go similarity index 97% rename from internal/api/repo.go rename to internal/api/v0/repo.go index 8ec3a966..bfd7e26a 100644 --- a/internal/api/repo.go +++ b/internal/api/v0/repo.go @@ -4,7 +4,7 @@ import ( "context" "time" - "github.com/massdriver-cloud/mass/internal/api/scalars" + "github.com/massdriver-cloud/mass/internal/api/v0/scalars" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" ) diff --git a/internal/api/repo_test.go b/internal/api/v0/repo_test.go similarity index 98% rename from internal/api/repo_test.go rename to internal/api/v0/repo_test.go index b2078a71..39489acf 100644 --- a/internal/api/repo_test.go +++ b/internal/api/v0/repo_test.go @@ -3,7 +3,7 @@ package api_test import ( "testing" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/gqlmock" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/config" diff --git a/internal/api/scalars/cursor.go b/internal/api/v0/scalars/cursor.go similarity index 100% rename from internal/api/scalars/cursor.go rename to internal/api/v0/scalars/cursor.go diff --git a/internal/api/scalars/json.go b/internal/api/v0/scalars/json.go similarity index 100% rename from internal/api/scalars/json.go rename to internal/api/v0/scalars/json.go diff --git a/internal/api/scalars/json_test.go b/internal/api/v0/scalars/json_test.go similarity index 90% rename from internal/api/scalars/json_test.go rename to internal/api/v0/scalars/json_test.go index f25ab02e..847c14a1 100644 --- a/internal/api/scalars/json_test.go +++ b/internal/api/v0/scalars/json_test.go @@ -4,7 +4,7 @@ import ( "reflect" "testing" - "github.com/massdriver-cloud/mass/internal/api/scalars" + "github.com/massdriver-cloud/mass/internal/api/v0/scalars" ) func TestMarshalJSON(t *testing.T) { diff --git a/internal/api/schema.graphql b/internal/api/v0/schema.graphql similarity index 100% rename from internal/api/schema.graphql rename to internal/api/v0/schema.graphql diff --git a/internal/api/server.go b/internal/api/v0/server.go similarity index 100% rename from internal/api/server.go rename to internal/api/v0/server.go diff --git a/internal/api/server_test.go b/internal/api/v0/server_test.go similarity index 93% rename from internal/api/server_test.go rename to internal/api/v0/server_test.go index 6875ed31..5c73badc 100644 --- a/internal/api/server_test.go +++ b/internal/api/v0/server_test.go @@ -3,7 +3,7 @@ package api_test import ( "testing" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/gqlmock" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" "github.com/stretchr/testify/assert" diff --git a/internal/api/urls.go b/internal/api/v0/urls.go similarity index 80% rename from internal/api/urls.go rename to internal/api/v0/urls.go index 50834b15..da472202 100644 --- a/internal/api/urls.go +++ b/internal/api/v0/urls.go @@ -38,18 +38,18 @@ func (u *URLHelper) ProjectsURL() string { } // ProjectURL returns the URL for a specific project -func (u *URLHelper) ProjectURL(projectSlug string) string { - return fmt.Sprintf("%s/orgs/%s/projects/%s/", u.baseURL, u.orgID, projectSlug) +func (u *URLHelper) ProjectURL(projectID string) string { + return fmt.Sprintf("%s/orgs/%s/projects/%s/", u.baseURL, u.orgID, projectID) } // EnvironmentURL returns the URL for a specific environment -func (u *URLHelper) EnvironmentURL(projectSlug, environmentSlug string) string { - return fmt.Sprintf("%s/orgs/%s/projects/%s/environments/%s", u.baseURL, u.orgID, projectSlug, environmentSlug) +func (u *URLHelper) EnvironmentURL(projectID, environmentID string) string { + return fmt.Sprintf("%s/orgs/%s/projects/%s/environments/%s", u.baseURL, u.orgID, projectID, environmentID) } -// PackageURL returns the URL for a specific package -func (u *URLHelper) PackageURL(projectSlug, environmentSlug, packageSlug string) string { - return fmt.Sprintf("%s/orgs/%s/projects/%s/environments/%s?package=%s", u.baseURL, u.orgID, projectSlug, environmentSlug, packageSlug) +// InstanceURL returns the URL for a specific package +func (u *URLHelper) InstanceURL(projectID, environmentID, instanceID string) string { + return fmt.Sprintf("%s/orgs/%s/projects/%s/environments/%s?package=%s", u.baseURL, u.orgID, projectID, environmentID, instanceID) } // BundleURL returns the URL for a specific bundle version diff --git a/internal/api/zz_generated.go b/internal/api/v0/zz_generated.go similarity index 99% rename from internal/api/zz_generated.go rename to internal/api/v0/zz_generated.go index 4bf8f720..d34b36b3 100644 --- a/internal/api/zz_generated.go +++ b/internal/api/v0/zz_generated.go @@ -9,7 +9,7 @@ import ( "time" "github.com/Khan/genqlient/graphql" - "github.com/massdriver-cloud/mass/internal/api/scalars" + "github.com/massdriver-cloud/mass/internal/api/v0/scalars" ) // How connecting this type works on the diagram. @@ -3875,7 +3875,7 @@ type getPackagePackage struct { // The semantic version that was last executed. // This reflects what has been provisioned to infrastructure, which may differ from `resolvedVersion` // if the package hasn't been deployed since the version was changed. Returns nil if never deployed. - DeployedVersion string `json:"deployedVersion"` + DeployedVersion *string `json:"deployedVersion"` // Package configuration parameters Params map[string]any `json:"-"` // The latest deployment for this package @@ -3902,7 +3902,7 @@ func (v *getPackagePackage) GetSlug() string { return v.Slug } func (v *getPackagePackage) GetStatus() PackageStatus { return v.Status } // GetDeployedVersion returns getPackagePackage.DeployedVersion, and is useful for accessing the field via an interface. -func (v *getPackagePackage) GetDeployedVersion() string { return v.DeployedVersion } +func (v *getPackagePackage) GetDeployedVersion() *string { return v.DeployedVersion } // GetParams returns getPackagePackage.Params, and is useful for accessing the field via an interface. func (v *getPackagePackage) GetParams() map[string]any { return v.Params } @@ -3974,7 +3974,7 @@ type __premarshalgetPackagePackage struct { Status PackageStatus `json:"status"` - DeployedVersion string `json:"deployedVersion"` + DeployedVersion *string `json:"deployedVersion"` Params json.RawMessage `json:"params"` diff --git a/internal/api/v1/blueprint.go b/internal/api/v1/blueprint.go new file mode 100644 index 00000000..fb7545f4 --- /dev/null +++ b/internal/api/v1/blueprint.go @@ -0,0 +1,7 @@ +// Package api provides a client for the Massdriver v1 GraphQL API. +package api + +// Blueprint represents the modeled infrastructure for an environment. +type Blueprint struct { + Instances []Instance `json:"instances,omitempty"` +} diff --git a/internal/api/v1/bundle.go b/internal/api/v1/bundle.go new file mode 100644 index 00000000..d3f56484 --- /dev/null +++ b/internal/api/v1/bundle.go @@ -0,0 +1,17 @@ +package api + +import ( + "time" +) + +// Bundle represents a Massdriver bundle (IaC module) and its metadata. +type Bundle struct { + ID string `json:"id"` + Name string `json:"name"` + Version string `json:"version"` + Description string `json:"description,omitempty"` + Icon string `json:"icon,omitempty"` + SourceURL string `json:"sourceUrl,omitempty"` + CreatedAt time.Time `json:"createdAt,omitempty"` + UpdatedAt time.Time `json:"updatedAt,omitempty"` +} diff --git a/internal/api/v1/cost.go b/internal/api/v1/cost.go new file mode 100644 index 00000000..d82368e1 --- /dev/null +++ b/internal/api/v1/cost.go @@ -0,0 +1,15 @@ +package api + +// CostSummary holds cost data for a resource. +type CostSummary struct { + LastMonth CostSample `json:"lastMonth" mapstructure:"lastMonth"` + MonthlyAverage CostSample `json:"monthlyAverage" mapstructure:"monthlyAverage"` + LastDay CostSample `json:"lastDay" mapstructure:"lastDay"` + DailyAverage CostSample `json:"dailyAverage" mapstructure:"dailyAverage"` +} + +// CostSample is a single cost measurement. Fields may be null when no cost data exists. +type CostSample struct { + Amount *float64 `json:"amount" mapstructure:"amount"` + Currency *string `json:"currency" mapstructure:"currency"` +} diff --git a/internal/api/v1/environment.go b/internal/api/v1/environment.go new file mode 100644 index 00000000..db4ec226 --- /dev/null +++ b/internal/api/v1/environment.go @@ -0,0 +1,142 @@ +package api + +import ( + "context" + "errors" + "fmt" + "strings" + + "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" + "github.com/mitchellh/mapstructure" +) + +// Environment represents a Massdriver deployment environment within a project. +type Environment struct { + ID string `json:"id" mapstructure:"id"` + Name string `json:"name" mapstructure:"name"` + Description string `json:"description,omitempty" mapstructure:"description"` + Cost CostSummary `json:"cost" mapstructure:"cost"` + Project *Project `json:"project,omitempty" mapstructure:"project,omitempty"` + Blueprint *Blueprint `json:"blueprint,omitempty" mapstructure:"-"` +} + +// GetEnvironment retrieves an environment by ID from the Massdriver API. +func GetEnvironment(ctx context.Context, mdClient *client.Client, id string) (*Environment, error) { + response, err := getEnvironment(ctx, mdClient.GQLv1, mdClient.Config.OrganizationID, id) + if err != nil { + return nil, fmt.Errorf("failed to get environment %s: %w", id, err) + } + + return toEnvironment(response.Environment) +} + +// ListEnvironments returns environments, optionally filtered. +func ListEnvironments(ctx context.Context, mdClient *client.Client, filter *EnvironmentsFilter) ([]Environment, error) { + response, err := listEnvironments(ctx, mdClient.GQLv1, mdClient.Config.OrganizationID, filter, nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to list environments: %w", err) + } + + envs := make([]Environment, 0, len(response.Environments.Items)) + for _, resp := range response.Environments.Items { + env, envErr := toEnvironment(resp) + if envErr != nil { + return nil, fmt.Errorf("failed to convert environment: %w", envErr) + } + envs = append(envs, *env) + } + + return envs, nil +} + +func toEnvironment(v any) (*Environment, error) { + env := Environment{} + if err := mapstructure.Decode(v, &env); err != nil { + return nil, fmt.Errorf("failed to decode environment: %w", err) + } + + // Unwrap paginated blueprint.instances (API returns {blueprint: {instances: {items: [...]}}}) + type instPage struct { + Items []Instance `mapstructure:"items"` + } + type blueprint struct { + Instances instPage `mapstructure:"instances"` + } + type hasBP struct { + Blueprint blueprint `mapstructure:"blueprint"` + } + var wrapper hasBP + if err := mapstructure.Decode(v, &wrapper); err == nil && len(wrapper.Blueprint.Instances.Items) > 0 { //nolint:musttag // internal unwrapping struct + env.Blueprint = &Blueprint{ + Instances: wrapper.Blueprint.Instances.Items, + } + } + + return &env, nil +} + +// CreateEnvironment creates a new environment within the given project. +func CreateEnvironment(ctx context.Context, mdClient *client.Client, projectID string, input CreateEnvironmentInput) (*Environment, error) { + response, err := createEnvironment(ctx, mdClient.GQLv1, mdClient.Config.OrganizationID, projectID, input) + if err != nil { + return nil, err + } + if !response.CreateEnvironment.Successful { + messages := response.CreateEnvironment.GetMessages() + if len(messages) > 0 { + var sb strings.Builder + sb.WriteString("unable to create environment:") + for _, msg := range messages { + sb.WriteString("\n - ") + sb.WriteString(msg.Message) + } + return nil, errors.New(sb.String()) + } + return nil, errors.New("unable to create environment") + } + return toEnvironment(response.CreateEnvironment.Result) +} + +// UpdateEnvironment updates an environment in the Massdriver API. +func UpdateEnvironment(ctx context.Context, mdClient *client.Client, id string, input UpdateEnvironmentInput) (*Environment, error) { + response, err := updateEnvironment(ctx, mdClient.GQLv1, mdClient.Config.OrganizationID, id, input) + if err != nil { + return nil, err + } + if !response.UpdateEnvironment.Successful { + messages := response.UpdateEnvironment.GetMessages() + if len(messages) > 0 { + var sb strings.Builder + sb.WriteString("unable to update environment:") + for _, msg := range messages { + sb.WriteString("\n - ") + sb.WriteString(msg.Message) + } + return nil, errors.New(sb.String()) + } + return nil, errors.New("unable to update environment") + } + return toEnvironment(response.UpdateEnvironment.Result) +} + +// DeleteEnvironment removes an environment by ID from the Massdriver API. +func DeleteEnvironment(ctx context.Context, mdClient *client.Client, id string) (*Environment, error) { + response, err := deleteEnvironment(ctx, mdClient.GQLv1, mdClient.Config.OrganizationID, id) + if err != nil { + return nil, err + } + if !response.DeleteEnvironment.Successful { + messages := response.DeleteEnvironment.GetMessages() + if len(messages) > 0 { + var sb strings.Builder + sb.WriteString("unable to delete environment:") + for _, msg := range messages { + sb.WriteString("\n - ") + sb.WriteString(msg.Message) + } + return nil, errors.New(sb.String()) + } + return nil, errors.New("unable to delete environment") + } + return toEnvironment(response.DeleteEnvironment.Result) +} diff --git a/internal/api/v1/environment_test.go b/internal/api/v1/environment_test.go new file mode 100644 index 00000000..7d0a9372 --- /dev/null +++ b/internal/api/v1/environment_test.go @@ -0,0 +1,132 @@ +package api_test + +import ( + "testing" + + api "github.com/massdriver-cloud/mass/internal/api/v1" + "github.com/massdriver-cloud/mass/internal/gqlmock" + "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" +) + +func TestGetEnvironment(t *testing.T) { + gqlClient := gqlmock.NewClientWithSingleJSONResponse(map[string]any{ + "data": map[string]any{ + "environment": map[string]any{ + "id": "env-uuid1", + "name": "Production", + "description": "Production environment", + "project": map[string]any{ + "id": "proj-1", + "name": "My Project", + }, + }, + }, + }) + mdClient := client.Client{ + GQLv1: gqlClient, + } + + env, err := api.GetEnvironment(t.Context(), &mdClient, "env-uuid1") + if err != nil { + t.Fatal(err) + } + + if env.ID != "env-uuid1" { + t.Errorf("got %s, wanted env-uuid1", env.ID) + } + if env.Name != "Production" { + t.Errorf("got %s, wanted Production", env.Name) + } + if env.Project == nil || env.Project.ID != "proj-1" { + t.Errorf("expected project with ID proj-1") + } +} + +func TestListEnvironments(t *testing.T) { + gqlClient := gqlmock.NewClientWithSingleJSONResponse(map[string]any{ + "data": map[string]any{ + "environments": map[string]any{ + "cursor": map[string]any{}, + "items": []map[string]any{ + { + "id": "env-1", + "name": "staging", + }, + { + "id": "env-2", + "name": "production", + }, + }, + }, + }, + }) + mdClient := client.Client{ + GQLv1: gqlClient, + } + + envs, err := api.ListEnvironments(t.Context(), &mdClient, nil) + if err != nil { + t.Fatal(err) + } + + if len(envs) != 2 { + t.Errorf("got %d environments, wanted 2", len(envs)) + } +} + +func TestCreateEnvironment(t *testing.T) { + gqlClient := gqlmock.NewClientWithSingleJSONResponse(map[string]any{ + "data": map[string]any{ + "createEnvironment": map[string]any{ + "result": map[string]any{ + "id": "env-new", + "name": "Staging", + "description": "Staging environment", + }, + "successful": true, + }, + }, + }) + mdClient := client.Client{ + GQLv1: gqlClient, + } + + env, err := api.CreateEnvironment(t.Context(), &mdClient, "proj-1", api.CreateEnvironmentInput{ + Id: "staging", + Name: "Staging", + Description: "Staging environment", + }) + if err != nil { + t.Fatal(err) + } + + if env.ID != "env-new" { + t.Errorf("got %s, wanted env-new", env.ID) + } +} + +func TestDeleteEnvironment(t *testing.T) { + gqlClient := gqlmock.NewClientWithSingleJSONResponse(map[string]any{ + "data": map[string]any{ + "deleteEnvironment": map[string]any{ + "result": map[string]any{ + "id": "env-1", + "name": "Staging", + }, + "successful": true, + }, + }, + }) + mdClient := client.Client{ + GQLv1: gqlClient, + } + + env, err := api.DeleteEnvironment(t.Context(), &mdClient, "env-1") + if err != nil { + t.Fatal(err) + } + + if env.ID != "env-1" { + t.Errorf("got %s, wanted env-1", env.ID) + } +} diff --git a/internal/api/v1/error.go b/internal/api/v1/error.go new file mode 100644 index 00000000..cb710a49 --- /dev/null +++ b/internal/api/v1/error.go @@ -0,0 +1,33 @@ +package api + +import ( + "fmt" +) + +// MutationError represents an error returned from a GraphQL mutation, including validation messages. +type MutationError struct { + Err string + Messages []MutationValidationError +} + +// MutationValidationError represents a single validation error from a mutation. +type MutationValidationError struct { + Code string + Field string + Message string +} + +func (m *MutationError) Error() string { + err := fmt.Sprintf("GraphQL mutation %s\n", m.Err) + + for _, msg := range m.Messages { + err = fmt.Sprintf("%s - %s\n", err, msg.Message) + } + + return err +} + +// NewMutationError creates a new MutationError with the given message and validation errors. +func NewMutationError(msg string, validationErrors []MutationValidationError) error { + return &MutationError{Err: msg, Messages: validationErrors} +} diff --git a/internal/api/v1/genqlient.graphql b/internal/api/v1/genqlient.graphql new file mode 100644 index 00000000..2f08ab4f --- /dev/null +++ b/internal/api/v1/genqlient.graphql @@ -0,0 +1,248 @@ +# PROJECTS + +query listProjects($organizationId: ID!) { + projects(organizationId: $organizationId, sort: {field: NAME, order: ASC}) { + items { + id + name + description + createdAt + updatedAt + cost { + monthlyAverage { + amount + currency + } + dailyAverage { + amount + currency + } + } + } + } +} + +query getProject($organizationId: ID!, $id: ID!) { + project(organizationId: $organizationId, id: $id) { + id + name + description + environments { + items { + id + name + description + createdAt + updatedAt + cost { + monthlyAverage { + amount + currency + } + dailyAverage { + amount + currency + } + } + } + } + createdAt + updatedAt + deletable { + result + } + cost { + monthlyAverage { + amount + currency + } + dailyAverage { + amount + currency + } + } + } +} + +mutation createProject($organizationId: ID!, $input: CreateProjectInput!) { + createProject(organizationId: $organizationId, input: $input) { + result { + id + name + description + } + successful + messages { + code + field + message + } + } +} + +mutation updateProject($organizationId: ID!, $id: ID!, $input: UpdateProjectInput!) { + updateProject(organizationId: $organizationId, id: $id, input: $input) { + result { + id + name + description + } + successful + messages { + code + field + message + } + } +} + +mutation deleteProject($organizationId: ID!, $id: ID!) { + deleteProject(organizationId: $organizationId, id: $id) { + result { + id + name + description + } + successful + messages { + code + field + message + } + } +} + + +# ENVIRONMENTS + +query listEnvironments( + $organizationId: ID!, + # @genqlient(omitempty: true, pointer: true) + $filter: EnvironmentsFilter, + # @genqlient(omitempty: true, pointer: true) + $sort: EnvironmentsSort, + # @genqlient(omitempty: true, pointer: true) + $cursor: Cursor +) { + environments(organizationId: $organizationId, filter: $filter, sort: $sort, cursor: $cursor) { + cursor { + next + previous + } + items { + id + name + description + createdAt + updatedAt + project { + id + name + } + cost { + monthlyAverage { + amount + currency + } + dailyAverage { + amount + currency + } + } + } + } +} + +query getEnvironment($organizationId: ID!, $id: ID!) { + environment(organizationId: $organizationId, id: $id) { + id + name + description + createdAt + updatedAt + cost { + monthlyAverage { + amount + currency + } + dailyAverage { + amount + currency + } + } + project { + id + name + description + } + blueprint { + instances { + cursor { + next + previous + } + items { + id + name + status + version + releaseStrategy + createdAt + updatedAt + bundle { + name + id + } + } + } + } + } +} + +mutation createEnvironment($organizationId: ID!, $projectId: ID!, $input: CreateEnvironmentInput!) { + createEnvironment(organizationId: $organizationId, projectId: $projectId, input: $input) { + result { + id + name + description + } + successful + messages { + code + field + message + } + } +} + +mutation updateEnvironment($organizationId: ID!, $id: ID!, $input: UpdateEnvironmentInput!) { + updateEnvironment(organizationId: $organizationId, id: $id, input: $input) { + result { + id + name + description + } + successful + messages { + code + field + message + } + } +} + +mutation deleteEnvironment($organizationId: ID!, $id: ID!) { + deleteEnvironment(organizationId: $organizationId, id: $id) { + result { + id + name + description + } + successful + messages { + code + field + message + } + } +} diff --git a/internal/api/v1/genqlient.yaml b/internal/api/v1/genqlient.yaml new file mode 100644 index 00000000..832be3b9 --- /dev/null +++ b/internal/api/v1/genqlient.yaml @@ -0,0 +1,29 @@ +# For full documentation see: +# https://github.com/Khan/genqlient/blob/main/docs/genqlient.yaml + +schema: schema.graphql +operations: + - genqlient.graphql +generated: zz_generated.go +package: api +bindings: + JSON: + type: map[string]any + marshaler: github.com/massdriver-cloud/mass/internal/api/v1/scalars.MarshalJSON + unmarshaler: github.com/massdriver-cloud/mass/internal/api/v1/scalars.UnmarshalJSON + Map: + type: map[string]any + marshaler: github.com/massdriver-cloud/mass/internal/api/v1/scalars.MarshalJSON + unmarshaler: github.com/massdriver-cloud/mass/internal/api/v1/scalars.UnmarshalJSON + DateTime: + type: time.Time + VersionConstraint: + type: string + Semver: + type: string + BundleId: + type: string + OciRepoName: + type: string + Upload: + type: string diff --git a/internal/api/v1/instance.go b/internal/api/v1/instance.go new file mode 100644 index 00000000..631d9afc --- /dev/null +++ b/internal/api/v1/instance.go @@ -0,0 +1,12 @@ +package api + +// Instance represents a deployed bundle instance within a Massdriver environment. +type Instance struct { + ID string `json:"id" mapstructure:"id"` + Name string `json:"name" mapstructure:"name"` + Status string `json:"status" mapstructure:"status"` + Version string `json:"version" mapstructure:"version"` + ReleaseStrategy string `json:"releaseStrategy" mapstructure:"releaseStrategy"` + Environment *Environment `json:"environment,omitempty" mapstructure:"environment,omitempty"` + Bundle *Bundle `json:"bundle,omitempty" mapstructure:"bundle,omitempty"` +} diff --git a/internal/api/v1/main.go b/internal/api/v1/main.go new file mode 100644 index 00000000..583f832b --- /dev/null +++ b/internal/api/v1/main.go @@ -0,0 +1,3 @@ +package api + +//go:generate go run github.com/Khan/genqlient diff --git a/internal/api/v1/project.go b/internal/api/v1/project.go new file mode 100644 index 00000000..1ac63123 --- /dev/null +++ b/internal/api/v1/project.go @@ -0,0 +1,136 @@ +package api + +import ( + "context" + "errors" + "fmt" + "strings" + + "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" + "github.com/mitchellh/mapstructure" +) + +// Project represents a Massdriver project. +type Project struct { + ID string `json:"id" mapstructure:"id"` + Name string `json:"name" mapstructure:"name"` + Description string `json:"description" mapstructure:"description"` + Cost CostSummary `json:"cost" mapstructure:"cost"` + Environments []Environment `json:"environments,omitempty" mapstructure:"-"` +} + +// GetProject retrieves a project by ID from the Massdriver API. +func GetProject(ctx context.Context, mdClient *client.Client, id string) (*Project, error) { + response, err := getProject(ctx, mdClient.GQLv1, mdClient.Config.OrganizationID, id) + if err != nil { + return nil, fmt.Errorf("failed to get project %s: %w", id, err) + } + + return toProject(response.Project) +} + +// ListProjects returns all projects for the configured organization. +func ListProjects(ctx context.Context, mdClient *client.Client) ([]Project, error) { + response, err := listProjects(ctx, mdClient.GQLv1, mdClient.Config.OrganizationID) + if err != nil { + return nil, fmt.Errorf("failed to list projects: %w", err) + } + + records := make([]Project, 0, len(response.Projects.Items)) + for _, resp := range response.Projects.Items { + proj, projErr := toProject(resp) + if projErr != nil { + return nil, fmt.Errorf("failed to convert project: %w", projErr) + } + records = append(records, *proj) + } + + return records, nil +} + +func toProject(p any) (*Project, error) { + proj := Project{} + if err := mapstructure.Decode(p, &proj); err != nil { + return nil, fmt.Errorf("failed to decode project: %w", err) + } + + // Unwrap paginated environments (API returns {items: [...]}) + type envPage struct { + Items []Environment `mapstructure:"items"` + } + type hasEnvs struct { + Environments envPage `mapstructure:"environments"` + } + var wrapper hasEnvs + if err := mapstructure.Decode(p, &wrapper); err == nil && len(wrapper.Environments.Items) > 0 { + proj.Environments = wrapper.Environments.Items + } + + return &proj, nil +} + +// CreateProject creates a new project in the Massdriver API. +func CreateProject(ctx context.Context, mdClient *client.Client, input CreateProjectInput) (*Project, error) { + response, err := createProject(ctx, mdClient.GQLv1, mdClient.Config.OrganizationID, input) + if err != nil { + return nil, err + } + if !response.CreateProject.Successful { + messages := response.CreateProject.GetMessages() + if len(messages) > 0 { + var sb strings.Builder + sb.WriteString("unable to create project:") + for _, msg := range messages { + sb.WriteString("\n - ") + sb.WriteString(msg.Message) + } + return nil, errors.New(sb.String()) + } + return nil, errors.New("unable to create project") + } + return toProject(response.CreateProject.Result) +} + +// UpdateProject updates a project in the Massdriver API. +func UpdateProject(ctx context.Context, mdClient *client.Client, id string, input UpdateProjectInput) (*Project, error) { + response, err := updateProject(ctx, mdClient.GQLv1, mdClient.Config.OrganizationID, id, input) + if err != nil { + return nil, err + } + if !response.UpdateProject.Successful { + messages := response.UpdateProject.GetMessages() + if len(messages) > 0 { + var sb strings.Builder + sb.WriteString("unable to update project:") + for _, msg := range messages { + sb.WriteString("\n - ") + sb.WriteString(msg.Message) + } + return nil, errors.New(sb.String()) + } + return nil, errors.New("unable to update project") + } + return toProject(response.UpdateProject.Result) +} + +// DeleteProject removes a project by ID from the Massdriver API. +func DeleteProject(ctx context.Context, mdClient *client.Client, id string) (*Project, error) { + response, err := deleteProject(ctx, mdClient.GQLv1, mdClient.Config.OrganizationID, id) + if err != nil { + return nil, err + } + if !response.DeleteProject.Successful { + messages := response.DeleteProject.GetMessages() + if len(messages) > 0 { + var sb strings.Builder + sb.WriteString("unable to delete project:") + for _, msg := range messages { + sb.WriteString("\n - ") + sb.WriteString(msg.Message) + } + return nil, errors.New(sb.String()) + } + return nil, errors.New("unable to delete project") + } + return toProject(response.DeleteProject.Result) +} diff --git a/internal/api/v1/project_test.go b/internal/api/v1/project_test.go new file mode 100644 index 00000000..46b21fd7 --- /dev/null +++ b/internal/api/v1/project_test.go @@ -0,0 +1,154 @@ +package api_test + +import ( + "testing" + + api "github.com/massdriver-cloud/mass/internal/api/v1" + "github.com/massdriver-cloud/mass/internal/gqlmock" + "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" +) + +func TestGetProject(t *testing.T) { + gqlClient := gqlmock.NewClientWithSingleJSONResponse(map[string]any{ + "data": map[string]any{ + "project": map[string]any{ + "id": "proj-uuid1", + "name": "My Project", + "description": "A test project", + }, + }, + }) + mdClient := client.Client{ + GQLv1: gqlClient, + } + + project, err := api.GetProject(t.Context(), &mdClient, "proj-uuid1") + if err != nil { + t.Fatal(err) + } + + if project.ID != "proj-uuid1" { + t.Errorf("got %s, wanted proj-uuid1", project.ID) + } + if project.Name != "My Project" { + t.Errorf("got %s, wanted My Project", project.Name) + } +} + +func TestListProjects(t *testing.T) { + gqlClient := gqlmock.NewClientWithSingleJSONResponse(map[string]any{ + "data": map[string]any{ + "projects": map[string]any{ + "cursor": map[string]any{}, + "items": []map[string]any{ + { + "id": "uuid1", + "name": "project1", + }, + { + "id": "uuid2", + "name": "project2", + }, + }, + }, + }, + }) + mdClient := client.Client{ + GQLv1: gqlClient, + } + + projects, err := api.ListProjects(t.Context(), &mdClient) + if err != nil { + t.Fatal(err) + } + + if len(projects) != 2 { + t.Errorf("got %d projects, wanted 2", len(projects)) + } +} + +func TestCreateProject(t *testing.T) { + gqlClient := gqlmock.NewClientWithSingleJSONResponse(map[string]any{ + "data": map[string]any{ + "createProject": map[string]any{ + "result": map[string]any{ + "id": "new-proj", + "name": "New Project", + "description": "A new project", + }, + "successful": true, + }, + }, + }) + mdClient := client.Client{ + GQLv1: gqlClient, + } + + project, err := api.CreateProject(t.Context(), &mdClient, api.CreateProjectInput{ + Id: "new-proj", + Name: "New Project", + Description: "A new project", + }) + if err != nil { + t.Fatal(err) + } + + if project.ID != "new-proj" { + t.Errorf("got %s, wanted new-proj", project.ID) + } + if project.Name != "New Project" { + t.Errorf("got %s, wanted New Project", project.Name) + } +} + +func TestCreateProjectFailure(t *testing.T) { + gqlClient := gqlmock.NewClientWithSingleJSONResponse(map[string]any{ + "data": map[string]any{ + "createProject": map[string]any{ + "result": nil, + "successful": false, + "messages": []map[string]any{ + { + "code": "required", + "field": "name", + "message": "name is required", + }, + }, + }, + }, + }) + mdClient := client.Client{ + GQLv1: gqlClient, + } + + _, err := api.CreateProject(t.Context(), &mdClient, api.CreateProjectInput{}) + if err == nil { + t.Fatal("expected error, got nil") + } +} + +func TestDeleteProject(t *testing.T) { + gqlClient := gqlmock.NewClientWithSingleJSONResponse(map[string]any{ + "data": map[string]any{ + "deleteProject": map[string]any{ + "result": map[string]any{ + "id": "proj-1", + "name": "Deleted Project", + }, + "successful": true, + }, + }, + }) + mdClient := client.Client{ + GQLv1: gqlClient, + } + + project, err := api.DeleteProject(t.Context(), &mdClient, "proj-1") + if err != nil { + t.Fatal(err) + } + + if project.ID != "proj-1" { + t.Errorf("got %s, wanted proj-1", project.ID) + } +} diff --git a/internal/api/v1/scalars/cursor.go b/internal/api/v1/scalars/cursor.go new file mode 100644 index 00000000..37c7dcaf --- /dev/null +++ b/internal/api/v1/scalars/cursor.go @@ -0,0 +1,10 @@ +// Package scalars provides custom GraphQL scalar types. +package scalars + +// Cursor represents pagination cursor with omitempty on all fields +// to avoid sending empty strings to the server +type Cursor struct { + Limit int `json:"limit,omitempty"` + Next string `json:"next,omitempty"` + Previous string `json:"previous,omitempty"` +} diff --git a/internal/api/v1/scalars/json.go b/internal/api/v1/scalars/json.go new file mode 100644 index 00000000..385b9509 --- /dev/null +++ b/internal/api/v1/scalars/json.go @@ -0,0 +1,19 @@ +package scalars + +import ( + "encoding/json" +) + +// MarshalJSON marshals a value twice to create an escaped string of JSON +func MarshalJSON(v any) ([]byte, error) { + bytes, err := json.Marshal(v) + if err != nil { + return nil, err + } + return json.Marshal(string(bytes)) +} + +// UnmarshalJSON unmarshals raw JSON bytes into the provided map. +func UnmarshalJSON(data []byte, v *map[string]any) error { + return json.Unmarshal(data, v) +} diff --git a/internal/api/v1/scalars/json_test.go b/internal/api/v1/scalars/json_test.go new file mode 100644 index 00000000..847c14a1 --- /dev/null +++ b/internal/api/v1/scalars/json_test.go @@ -0,0 +1,34 @@ +package scalars_test + +import ( + "reflect" + "testing" + + "github.com/massdriver-cloud/mass/internal/api/v0/scalars" +) + +func TestMarshalJSON(t *testing.T) { + data := map[string]any{"foo": "bar"} + got, _ := scalars.MarshalJSON(data) + + want := `"{\"foo\":\"bar\"}"` + + if string(got) != want { + t.Errorf("got %s, wanted %s", got, want) + } +} + +func TestUnmarshalJSON(t *testing.T) { + want := map[string]any{"foo": "bar"} + + data := []byte(`{"foo": "bar"}`) + got := map[string]any{} + + if err := scalars.UnmarshalJSON(data, &got); err != nil { + t.Errorf("unexpected error: %v", err) + } + + if !reflect.DeepEqual(got, want) { + t.Errorf("got %v, wanted %v", got, want) + } +} diff --git a/internal/api/v1/schema.graphql b/internal/api/v1/schema.graphql new file mode 100644 index 00000000..1624ee24 --- /dev/null +++ b/internal/api/v1/schema.graphql @@ -0,0 +1,2952 @@ +schema { + mutation: RootMutationType + query: RootQueryType +} + +"Links a mutation to its JSON Schema and UI Schema for form generation" +directive @inputs( + "URL to the UI Schema" + ui: String! + + "URL to the JSON Schema" + schema: String! + + "Schema name matching the mutation (e.g., createProject)" + name: String! +) on FIELD_DEFINITION + +type RootQueryType { + "List all projects you have access to. Returns a paginated list sorted by name." + projects( + "Your organization ID" + organizationId: ID! + + "How to sort results" + sort: ProjectsSort + + "Pagination cursor from a previous response" + cursor: Cursor + ): ProjectsPage + + "Get a single project by its ID." + project( + "Your organization ID" + organizationId: ID! + + "The project ID" + id: ID! + ): Project + + "List all environments you have access to. Returns a paginated list sorted by name." + environments( + "Your organization ID" + organizationId: ID! + + "Filter results" + filter: EnvironmentsFilter + + "How to sort results" + sort: EnvironmentsSort + + "Pagination cursor from a previous response" + cursor: Cursor + ): EnvironmentsPage + + "Get a single environment by its ID." + environment( + "Your organization ID" + organizationId: ID! + + "The environment ID" + id: ID! + ): Environment + + "List all instances you have access to. Returns a paginated list sorted by name." + instances( + "Your organization ID" + organizationId: ID! + + "Filter results" + filter: InstancesFilter + + "How to sort results" + sort: InstancesSort + + "Pagination cursor from a previous response" + cursor: Cursor + ): InstancesPage + + "Get a single instance by its ID." + instance( + "Your organization ID" + organizationId: ID! + + "The instance ID" + id: ID! + ): Instance + + """ + Get the configuration fields available across your instances. + + Returns all the unique fields from your instance configurations that you can + use to build search filters or dashboards. For example, if your instances have + 'database.instance_type' or 'cluster.node_count' fields, they'll appear here. + """ + paramDimensions( + "Your organization ID" + organizationId: ID! + + "Scope which instances' schemas to introspect" + filter: ParamDimensionsFilter + + "How to sort results" + sort: ParamDimensionsSort + + "Pagination cursor from a previous response" + cursor: Cursor + ): ParamDimensionsPage + + "List all deployments you have access to. Returns a paginated list, newest first." + deployments( + "Your organization ID" + organizationId: ID! + + "Filter results" + filter: DeploymentsFilter + + "How to sort results" + sort: DeploymentsSort + + "Pagination cursor from a previous response" + cursor: Cursor + ): DeploymentsPage + + "Get a single deployment by its ID." + deployment( + "Your organization ID" + organizationId: ID! + + "The deployment ID" + id: ID! + ): Deployment + + """ + Get a bundle by its identifier. + + The ID format is `name@version` where version can be an exact version, + a constraint, or a channel: + + - `aws-aurora-postgres@1.2.3` — exact version + - `aws-aurora-postgres@~1.2` — latest patch in 1.2.x + - `aws-aurora-postgres@~1` — latest minor in 1.x.x + - `aws-aurora-postgres@latest` — newest stable release + - `aws-aurora-postgres@latest+dev` — newest release including dev builds + - `aws-aurora-postgres` — shorthand for `name@latest` + """ + bundle( + "Your organization ID" + organizationId: ID! + + "Bundle identifier (e.g., 'aws-aurora-postgres@1.2.3' or 'aws-aurora-postgres@latest')" + id: BundleId! + ): Bundle + + "List all groups in your organization. Returns a paginated list." + groups( + "Your organization ID" + organizationId: ID! + + "How to sort results" + sort: GroupsSort + + "Pagination cursor from a previous response" + cursor: Cursor + ): GroupsPage + + "Get a single group by its ID." + group( + "Your organization ID" + organizationId: ID! + + "The group ID" + id: ID! + ): Group + + "Get your organization and its settings." + organization( + "Your organization ID" + organizationId: ID! + ): Organization + + "Get information about yourself (the authenticated user or service account)." + viewer: Viewer + + "Get server info and available authentication methods. No authentication required." + server: Server! + + "List all supported integration types. Use these to discover what integrations are available and their configuration schemas." + integrationTypes( + "Your organization ID" + organizationId: ID! + ): IntegrationTypesPage + + "List your organization's configured integrations." + integrations( + "Your organization ID" + organizationId: ID! + + "Filter results" + filter: IntegrationsFilter + + "How to sort results" + sort: IntegrationsSort + + "Pagination cursor from a previous response" + cursor: Cursor + ): IntegrationsPage + + "Get a single integration by its type ID." + integration( + "Your organization ID" + organizationId: ID! + + "The integration type ID (e.g., 'aws-cost-and-usage-reports')" + id: ID! + ): Integration + + "List audit log events for your organization. Returns newest first by default." + auditLogs( + "Your organization ID" + organizationId: ID! + + "Filter results" + filter: AuditLogsFilter + + "How to sort results" + sort: AuditLogsSort + + "Pagination cursor from a previous response" + cursor: Cursor + ): AuditLogsPage + + "Get a single audit log event by its ID." + auditLog( + "Your organization ID" + organizationId: ID! + + "The audit log event ID" + id: ID! + ): AuditLog +} + +type RootMutationType { + "Create a new project in your organization." + createProject( + "Your organization ID" + organizationId: ID! + + "Create a new project. A project is the complete model of your application—its infrastructure, architecture, configurations, and environments." + input: CreateProjectInput! + ): ProjectPayload @inputs(name: "createProject", schema: "\/graphql\/v1\/inputs\/createProject.json", ui: "\/graphql\/v1\/inputs\/createProject.ui.json") + + "Update a project's name or description." + updateProject( + "Your organization ID" + organizationId: ID! + + "The project ID to update" + id: ID! + + "Update an existing project's name and description. The ID cannot be changed after creation." + input: UpdateProjectInput! + ): ProjectPayload @inputs(name: "updateProject", schema: "\/graphql\/v1\/inputs\/updateProject.json", ui: "\/graphql\/v1\/inputs\/updateProject.ui.json") + + "Delete a project. You must delete all environments first." + deleteProject( + "Your organization ID" + organizationId: ID! + + "The project ID to delete" + id: ID! + ): ProjectPayload + + "Create a new environment in a project." + createEnvironment( + "Your organization ID" + organizationId: ID! + + "The project to create the environment in" + projectId: ID! + + "Create a new environment. Environments are isolated deployment contexts like production, staging, or development, each with independent secrets and configurations." + input: CreateEnvironmentInput! + ): EnvironmentPayload @inputs(name: "createEnvironment", schema: "\/graphql\/v1\/inputs\/createEnvironment.json", ui: "\/graphql\/v1\/inputs\/createEnvironment.ui.json") + + "Update an environment's name or description." + updateEnvironment( + "Your organization ID" + organizationId: ID! + + "The environment ID to update" + id: ID! + + "Update an existing environment's name and description. The ID cannot be changed after creation." + input: UpdateEnvironmentInput! + ): EnvironmentPayload @inputs(name: "updateEnvironment", schema: "\/graphql\/v1\/inputs\/updateEnvironment.json", ui: "\/graphql\/v1\/inputs\/updateEnvironment.ui.json") + + "Delete an environment. You must decommission all packages first." + deleteEnvironment( + "Your organization ID" + organizationId: ID! + + "The environment ID to delete" + id: ID! + ): EnvironmentPayload + + "Add a component to a project's blueprint." + addComponent( + "Your organization ID" + organizationId: ID! + + "The project ID" + projectId: ID! + + "Add an infrastructure component to a project's blueprint. Each component is a specific instance of a bundle (like a Redis cache or PostgreSQL database) that composes with other components to form your application." + input: AddComponentInput! + ): ComponentPayload @inputs(name: "addComponent", schema: "\/graphql\/v1\/inputs\/addComponent.json", ui: "\/graphql\/v1\/inputs\/addComponent.ui.json") + + "Remove a component from a project's blueprint." + removeComponent( + "Your organization ID" + organizationId: ID! + + "The project ID" + projectId: ID! + + "The component ID to remove" + id: ID! + ): ComponentPayload + + "Create a link between two components." + linkComponents( + "Your organization ID" + organizationId: ID! + + "The project ID" + projectId: ID! + + "Create a link between two components in a project's blueprint. Links connect an output field on the source component to an input field on the destination component, establishing data flow between infrastructure resources." + input: LinkComponentsInput! + ): LinkPayload @inputs(name: "linkComponents", schema: "\/graphql\/v1\/inputs\/linkComponents.json", ui: "\/graphql\/v1\/inputs\/linkComponents.ui.json") + + "Remove a link between two components." + unlinkComponents( + "Your organization ID" + organizationId: ID! + + "The link ID to remove" + id: ID! + ): LinkPayload + + "Set the position of a component on the canvas." + setComponentPosition( + "Your organization ID" + organizationId: ID! + + "The project ID" + projectId: ID! + + "The component ID" + id: ID! + + "Set the position of a component on the canvas." + input: SetComponentPositionInput! + ): ComponentPayload @inputs(name: "setComponentPosition", schema: "\/graphql\/v1\/inputs\/setComponentPosition.json", ui: "\/graphql\/v1\/inputs\/setComponentPosition.ui.json") + + "Accept a pending group invitation." + acceptGroupInvite( + "The invitation ID to accept" + id: ID! + ): InviteViewerPayload + + "Upload or replace your profile avatar. Accepts PNG, JPG, GIF, or WebP (max 1 MB)." + setAccountAvatar( + "The image file to upload" + avatar: Upload! + ): AvatarViewerPayload + + "Remove your profile avatar." + removeAccountAvatar: AvatarViewerPayload + + "Create a new group in your organization." + createGroup( + "Your organization ID" + organizationId: ID! + + "Create a new group. Groups control which projects members can access." + input: CreateGroupInput! + ): GroupPayload @inputs(name: "createGroup", schema: "\/graphql\/v1\/inputs\/createGroup.json", ui: "\/graphql\/v1\/inputs\/createGroup.ui.json") + + "Update a group's name or description." + updateGroup( + "Your organization ID" + organizationId: ID! + + "The group ID to update" + id: ID! + + "Update a group's name or description." + input: UpdateGroupInput! + ): GroupPayload @inputs(name: "updateGroup", schema: "\/graphql\/v1\/inputs\/updateGroup.json", ui: "\/graphql\/v1\/inputs\/updateGroup.ui.json") + + "Delete a custom group. Predefined organization groups cannot be deleted." + deleteGroup( + "Your organization ID" + organizationId: ID! + + "The group ID to delete" + id: ID! + ): GroupPayload + + "Invite a user to a group by email address." + createGroupInvitation( + "Your organization ID" + organizationId: ID! + + "The group ID to invite the user to" + groupId: ID! + + "Email address of the user to invite" + email: String! + ): GroupInvitationPayload + + "Remove a pending invitation from a group." + deleteGroupInvitation( + "Your organization ID" + organizationId: ID! + + "The group ID" + groupId: ID! + + "Email address of the invitation to remove" + email: String! + ): GroupInvitationPayload + + "Remove a member from a group." + deleteGroupMember( + "Your organization ID" + organizationId: ID! + + "The group ID" + groupId: ID! + + "Email address of the member to remove" + email: String! + ): GroupMemberPayload + + "Create a new tag constraint in your organization." + createOrganizationTagConstraint( + "Your organization ID" + organizationId: ID! + + "Define a structural tag constraint for your organization. Tag constraints control which tags can be set on resources at each level of the hierarchy." + input: CreateOrganizationTagConstraintInput! + ): OrganizationTagConstraintPayload @inputs(name: "createOrganizationTagConstraint", schema: "\/graphql\/v1\/inputs\/createOrganizationTagConstraint.json", ui: "\/graphql\/v1\/inputs\/createOrganizationTagConstraint.ui.json") + + "Delete a tag constraint from your organization." + deleteOrganizationTagConstraint( + "Your organization ID" + organizationId: ID! + + "The tag constraint ID to delete" + id: ID! + ): OrganizationTagConstraintPayload + + "Create a new organization. You become the owner and first admin." + createOrganization( + "Create a new organization. An organization is the top-level container for all your projects, environments, and infrastructure resources." + input: CreateOrganizationInput! + ): OrganizationPayload @inputs(name: "createOrganization", schema: "\/graphql\/v1\/inputs\/createOrganization.json", ui: "\/graphql\/v1\/inputs\/createOrganization.ui.json") + + "Upload or replace your organization's logo. Accepts PNG, JPG, GIF, or WebP (max 1 MB)." + setOrganizationLogo( + "Your organization ID" + organizationId: ID! + + "The image file to upload" + logo: Upload! + ): LogoOrganizationPayload + + "Remove your organization's logo." + removeOrganizationLogo( + "Your organization ID" + organizationId: ID! + ): LogoOrganizationPayload + + "Remove a member from the organization. Deletes all group memberships and pending invitations." + deleteOrganizationMember( + "Your organization ID" + organizationId: ID! + + "Email address of the member to remove" + email: String! + ): DeletedOrganizationMemberPayload + + "Create and activate an integration for your organization." + createIntegration( + "Your organization ID" + organizationId: ID! + + "The integration type to configure (e.g., 'aws-cost-and-usage-reports')" + id: ID! + + "Create and activate an integration for your organization. The config and auth payloads must conform to the integration type's configSchema and authSchema respectively." + input: CreateIntegrationInput! + ): IntegrationActivationPayload @inputs(name: "createIntegration", schema: "\/graphql\/v1\/inputs\/createIntegration.json", ui: "\/graphql\/v1\/inputs\/createIntegration.ui.json") + + "Delete an integration. This disables it first, then removes the configuration." + deleteIntegration( + "Your organization ID" + organizationId: ID! + + "The integration type ID to delete" + id: ID! + ): IntegrationPayload + + "Create a scoped, time-limited access token. The token value is only returned in this response." + createAccessToken( + "Your organization ID" + organizationId: ID! + + "Create a scoped, time-limited access token for API authentication." + input: CreateAccessTokenInput! + ): AccessTokenWithValuePayload @inputs(name: "createAccessToken", schema: "\/graphql\/v1\/inputs\/createAccessToken.json", ui: "\/graphql\/v1\/inputs\/createAccessToken.ui.json") + + "Revoke an access token. The token immediately stops working but the record is preserved for audit." + revokeAccessToken( + "Your organization ID" + organizationId: ID! + + "The access token ID to revoke" + id: ID! + ): AccessTokenPayload + + "Create a new service account. The secret token is only returned in this response." + createServiceAccount( + "Your organization ID" + organizationId: ID! + + "Create a new service account for programmatic API access." + input: CreateServiceAccountInput! + ): ServiceAccountWithSecretPayload @inputs(name: "createServiceAccount", schema: "\/graphql\/v1\/inputs\/createServiceAccount.json", ui: "\/graphql\/v1\/inputs\/createServiceAccount.ui.json") + + "Delete a service account. This action cannot be undone." + deleteServiceAccount( + "Your organization ID" + organizationId: ID! + + "The service account ID to delete" + id: ID! + ): ServiceAccountPayload + + "Add a service account to a group, granting it the group's access level." + addServiceAccountToGroup( + "Your organization ID" + organizationId: ID! + + "The service account ID" + serviceAccountId: ID! + + "The group ID to add the service account to" + groupId: ID! + ): ServiceAccountGroupPayload + + "Remove a service account from a group." + removeServiceAccountFromGroup( + "Your organization ID" + organizationId: ID! + + "The service account ID" + serviceAccountId: ID! + + "The group ID to remove the service account from" + groupId: ID! + ): ServiceAccountGroupPayload + + """ + Set a remote reference on an instance. + + Links an instance's resource field to a resource from another project or + an imported resource. The instance must not be in a provisioned or failed state. + """ + setRemoteReference( + "Your organization ID" + organizationId: ID! + + "Link an instance's resource field to a resource from another project or an imported resource. The instance must not be in a provisioned or failed state." + input: SetRemoteReferenceInput! + ): RemoteReferencePayload @inputs(name: "setRemoteReference", schema: "\/graphql\/v1\/inputs\/setRemoteReference.json", ui: "\/graphql\/v1\/inputs\/setRemoteReference.ui.json") + + """ + Remove a remote reference from an instance. + + The reference can only be removed if no provisioned instances are connected + through it. Removing the last remote reference resets the instance status + from EXTERNAL back to INITIALIZED. + """ + removeRemoteReference( + "Your organization ID" + organizationId: ID! + + "Remove a remote reference from an instance. The reference can only be removed if no provisioned instances are connected through it." + input: RemoveRemoteReferenceInput! + ): RemoteReferencePayload @inputs(name: "removeRemoteReference", schema: "\/graphql\/v1\/inputs\/removeRemoteReference.json", ui: "\/graphql\/v1\/inputs\/removeRemoteReference.ui.json") +} + +"Link an instance's resource field to a resource from another project or an imported resource. The instance must not be in a provisioned or failed state." +input SetRemoteReferenceInput { + "The resource field to assign the reference to" + field: String! + + "The instance to set the remote reference on" + instanceId: ID! + + "The resource to reference — either a UUID for imported resources or 'instance.field' for provisioned resources" + resourceId: ID! +} + +"Remove a remote reference from an instance. The reference can only be removed if no provisioned instances are connected through it." +input RemoveRemoteReferenceInput { + "The resource field to remove the reference from" + field: String! + + "The instance the reference belongs to" + instanceId: ID! +} + +"A remote reference linking an instance to an external resource." +type RemoteReference { + "Unique identifier" + id: ID! + + "The resource field this reference is assigned to" + field: String! + + "When this remote reference was created (UTC)" + createdAt: DateTime! + + "When this remote reference was last modified (UTC)" + updatedAt: DateTime! + + "The external resource this reference points to." + resource: Resource! +} + +type RemoteReferencePayload { + "Indicates if the mutation completed successfully or not." + successful: Boolean! + + "A list of failed validations. May be blank or null if mutation succeeded." + messages: [ValidationMessage] + + "The object created\/updated\/deleted by the mutation. May be null if mutation failed." + result: RemoteReference +} + +"A service account." +type ServiceAccount { + "Service account identifier" + id: ID! + + "Service account name" + name: String! + + "What this service account is for" + description: String + + "When this service account was created (UTC)" + createdAt: DateTime! + + "When this service account was last modified (UTC)" + updatedAt: DateTime! +} + +"A service account with its secret token. The secret is only visible on creation." +type ServiceAccountWithSecret { + "Service account identifier" + id: ID! + + "Service account name" + name: String! + + "What this service account is for" + description: String + + "Secret token for authentication. Only returned on creation." + secret: String! + + "When this service account was created (UTC)" + createdAt: DateTime! + + "When this service account was last modified (UTC)" + updatedAt: DateTime! +} + +type ServiceAccountPayload { + "Indicates if the mutation completed successfully or not." + successful: Boolean! + + "A list of failed validations. May be blank or null if mutation succeeded." + messages: [ValidationMessage] + + "The object created\/updated\/deleted by the mutation. May be null if mutation failed." + result: ServiceAccount +} + +type ServiceAccountWithSecretPayload { + "Indicates if the mutation completed successfully or not." + successful: Boolean! + + "A list of failed validations. May be blank or null if mutation succeeded." + messages: [ValidationMessage] + + "The object created\/updated\/deleted by the mutation. May be null if mutation failed." + result: ServiceAccountWithSecret +} + +type ServiceAccountGroupPayload { + "Indicates if the mutation completed successfully or not." + successful: Boolean! + + "A list of failed validations. May be blank or null if mutation succeeded." + messages: [ValidationMessage] + + "The object created\/updated\/deleted by the mutation. May be null if mutation failed." + result: ServiceAccount +} + +"Create a new service account for programmatic API access." +input CreateServiceAccountInput { + "What this service account is used for" + description: String + + "A human-readable name for the service account" + name: String! +} + +"An access token with the raw token value. Only returned on creation." +type AccessTokenWithValue { + "Access token identifier" + id: ID! + + "Label for this token" + name: String! + + "The full token value. Store securely — this is the only time it is shown." + token: String! + + "Short prefix for identifying this token in lists (e.g., md_a1b2c3d4)" + prefix: String! + + "Permission scopes granted to this token" + scopes: [String!]! + + "When this token expires (UTC)" + expiresAt: DateTime! + + "When this token was created (UTC)" + createdAt: DateTime! +} + +"An access token's metadata. Does not include the raw token value." +type AccessToken { + "Access token identifier" + id: ID! + + "Label for this token" + name: String! + + "Short prefix for identifying this token (e.g., md_a1b2c3d4)" + prefix: String! + + "Permission scopes granted to this token" + scopes: [String!]! + + "When this token expires (UTC)" + expiresAt: DateTime! + + "When this token was revoked, if applicable (UTC)" + revokedAt: DateTime + + "When this token was last used for authentication (UTC)" + lastUsedAt: DateTime + + "When this token was created (UTC)" + createdAt: DateTime! +} + +type AccessTokenWithValuePayload { + "Indicates if the mutation completed successfully or not." + successful: Boolean! + + "A list of failed validations. May be blank or null if mutation succeeded." + messages: [ValidationMessage] + + "The object created\/updated\/deleted by the mutation. May be null if mutation failed." + result: AccessTokenWithValue +} + +type AccessTokenPayload { + "Indicates if the mutation completed successfully or not." + successful: Boolean! + + "A list of failed validations. May be blank or null if mutation succeeded." + messages: [ValidationMessage] + + "The object created\/updated\/deleted by the mutation. May be null if mutation failed." + result: AccessToken +} + +"Create a scoped, time-limited access token for API authentication." +input CreateAccessTokenInput { + "How many minutes until this token expires. Defaults to 60 (1 hour). Maximum ~5,256,000 (10 years)." + expiresInMinutes: Int + + "A label to identify this token (e.g., 'CI deploy key')" + name: String! + + "Permission scopes. At least one required. Currently only [\"*\"] (full access) is supported." + scopes: [String!]! + + "Create the token on behalf of this service account. Omit to create for the authenticated identity." + serviceAccountId: String +} + +"The type of actor that performed an action." +enum AuditLogActorType { + "Human user account" + ACCOUNT + + "Service account (API key)" + SERVICE_ACCOUNT + + "Automated deployment" + DEPLOYMENT + + "System action or legacy event" + SYSTEM +} + +"Available fields for sorting audit logs." +enum AuditLogsSortField { + "Sort by when the event occurred" + OCCURRED_AT + + "Sort by event type" + TYPE +} + +"Sorting options for the audit logs list." +input AuditLogsSort { + "Which field to sort by" + field: AuditLogsSortField! + + "Sort direction" + order: SortOrder! +} + +"Filter by the type of actor that performed the action." +input AuditLogActorTypeFilter { + "Exact match" + eq: AuditLogActorType + + "Match any of these actor types" + in: [AuditLogActorType!] +} + +"Filter which audit logs to return." +input AuditLogsFilter { + "Filter by when the event occurred" + occurredAt: DatetimeFilter + + "Filter by event type (e.g., 'project.created')" + type: StringFilter + + "Filter by who performed the action" + actorType: AuditLogActorTypeFilter + + "Filter by the actor's ID" + actorId: IdFilter +} + +"The actor that performed an audit-logged action." +type V1AuditLogActor { + "Actor's unique identifier" + id: ID! + + "What kind of actor this is" + type: AuditLogActorType! + + "Display name (email for users, name for service accounts)" + name: String! +} + +"An audit log event." +type AuditLog { + "Unique event identifier" + id: ID! + + "When the event occurred (UTC)" + occurredAt: DateTime! + + "Event type (e.g., 'project.created')" + type: String! + + "Where the event originated" + source: String! + + "Resource the event applies to (MRI format)" + subject: String + + "Event payload with context and resource details" + data: Map + + "Who performed this action" + actor: V1AuditLogActor! +} + +type AuditLogsPage { + "Pagination cursors" + cursor: PaginationCursor! + + "A list of type audit_log." + items: [AuditLog] +} + +"The current status of an integration." +enum IntegrationStatus { + "Integration is inactive" + DISABLED + + "Integration is being deactivated" + DISABLING + + "Integration is being activated" + ENABLING + + "Integration is active and running" + ENABLED +} + +"Available fields for sorting integrations." +enum IntegrationsSortField { + "Sort by when the integration was created" + CREATED_AT + + "Sort alphabetically by integration type" + ID +} + +"Filter for integration status." +input IntegrationStatusFilter { + "Exact match" + eq: IntegrationStatus + + "Match any of these statuses" + in: [IntegrationStatus!] +} + +"Filter options for the integrations list." +input IntegrationsFilter { + "Filter by integration type ID" + id: StringFilter + + "Filter by status" + status: IntegrationStatusFilter +} + +"Sorting options for the integrations list." +input IntegrationsSort { + "Which field to sort by" + field: IntegrationsSortField! + + "Sort direction" + order: SortOrder! +} + +"Create and activate an integration for your organization. The config and auth payloads must conform to the integration type's configSchema and authSchema respectively." +input CreateIntegrationInput { + "Authentication credentials. Must conform to the integration type's authSchema. Write-only — not returned in queries." + auth: Map! + + "Integration-specific configuration. Must conform to the integration type's configSchema." + config: Map! +} + +"A supported integration type that can be configured for your organization." +type IntegrationTypeInfo { + "Unique identifier for this integration type" + id: String! + + "Display name" + name: String! + + "What this integration does" + description: String + + "URL to the documentation" + docs: String! + + "JSON Schema for the `config` field when creating this integration" + configSchema: Map! + + "JSON Schema for the `auth` field when creating this integration" + authSchema: Map! +} + +type IntegrationTypesPage { + "Pagination cursors" + cursor: PaginationCursor! + + "A list of type integration_type_info." + items: [IntegrationTypeInfo] +} + +"A configured integration." +type Integration { + "Integration type identifier (unique per organization)" + id: ID! + + "The type of integration" + integrationTypeId: String! + + "Integration-specific configuration" + config: Map! + + "Current status" + status: IntegrationStatus! + + "When this integration was created (UTC)" + createdAt: DateTime! + + "When this integration was last modified (UTC)" + updatedAt: DateTime! + + "When this integration will next execute" + nextRunAt: DateTime +} + +"A newly created integration with setup instructions." +type IntegrationActivation { + "Integration type identifier" + id: ID! + + "The type of integration" + integrationTypeId: String! + + "Integration-specific configuration" + config: Map! + + "Current status" + status: IntegrationStatus! + + "When this integration was created (UTC)" + createdAt: DateTime! + + "When this integration was last modified (UTC)" + updatedAt: DateTime! + + "When this integration will next execute" + nextRunAt: DateTime + + "Setup instructions for this integration. May contain sensitive credentials — store securely." + instructions: String! +} + +type IntegrationsPage { + "Pagination cursors" + cursor: PaginationCursor! + + "A list of type integration." + items: [Integration] +} + +type IntegrationPayload { + "Indicates if the mutation completed successfully or not." + successful: Boolean! + + "A list of failed validations. May be blank or null if mutation succeeded." + messages: [ValidationMessage] + + "The object created\/updated\/deleted by the mutation. May be null if mutation failed." + result: Integration +} + +type IntegrationActivationPayload { + "Indicates if the mutation completed successfully or not." + successful: Boolean! + + "A list of failed validations. May be blank or null if mutation succeeded." + messages: [ValidationMessage] + + "The object created\/updated\/deleted by the mutation. May be null if mutation failed." + result: IntegrationActivation +} + +"How this Massdriver instance is deployed." +enum ServerMode { + "Self-hosted in your own infrastructure" + SELF_HOSTED + + "Massdriver's managed cloud service" + MANAGED +} + +"Type of email-based authentication." +enum EmailAuthMethodType { + "Sign in with a passkey (WebAuthn)" + PASSKEY +} + +"An SSO provider available for authentication." +type SsoProvider { + "Provider name (e.g., 'google', 'okta')" + name: String! + + "URL to start the SSO login flow" + loginUrl: String! + + "Icon to display on the login button" + uiIconUrl: String + + "Label to display on the login button" + uiLabel: String +} + +"An email-based authentication method." +type EmailAuthMethod { + "Authentication type" + name: EmailAuthMethodType! +} + +"Information about this Massdriver server." +type Server { + "Base URL of the application" + appUrl: String! + + "Server version (e.g., '1.2.3')" + version: String! + + "Whether this is self-hosted or managed" + mode: ServerMode! + + "SSO providers available for login" + ssoProviders: [SsoProvider] + + "Email auth methods available" + emailAuthMethods: [EmailAuthMethod] +} + +"The resource level where a tag constraint applies." +enum TagConstraintScope { + "Tag is set on the organization" + ORGANIZATION + + "Tag is set on projects" + PROJECT + + "Tag is set on environments" + ENVIRONMENT + + "Tag is set on components" + COMPONENT + + "Tag is set on instances" + INSTANCE +} + +"Available fields for sorting tag constraints." +enum OrganizationTagConstraintsSortField { + "Sort alphabetically by tag key" + KEY + + "Sort by resource scope level" + SCOPE + + "Sort by when the constraint was created" + CREATED_AT +} + +"Sorting options for the tag constraints list." +input OrganizationTagConstraintsSort { + "Which field to sort by" + field: OrganizationTagConstraintsSortField! + + "Sort direction" + order: SortOrder! +} + +"Define a structural tag constraint for your organization. Tag constraints control which tags can be set on resources at each level of the hierarchy." +input CreateOrganizationTagConstraintInput { + "The tag key name. Must start with a letter. Keys starting with MD_ are reserved." + key: String! + + "Whether this tag must be set when creating a resource at the specified scope." + required: Boolean + + "The resource level where this tag is set. Values cascade to child resources." + scope: TagConstraintScope! +} + +"A tag constraint." +type OrganizationTagConstraint { + "Unique identifier" + id: ID! + + "The tag key name (e.g. TEAM, DOMAIN)" + key: String! + + "Resource level where this tag is set" + scope: TagConstraintScope! + + "Whether this tag must be set when creating a resource at the specified scope" + required: Boolean! + + "When this constraint was created (UTC)" + createdAt: DateTime! + + "When this constraint was last modified (UTC)" + updatedAt: DateTime! +} + +type OrganizationTagConstraintsPage { + "Pagination cursors" + cursor: PaginationCursor! + + "A list of type organization_tag_constraint." + items: [OrganizationTagConstraint] +} + +type OrganizationTagConstraintPayload { + "Indicates if the mutation completed successfully or not." + successful: Boolean! + + "A list of failed validations. May be blank or null if mutation succeeded." + messages: [ValidationMessage] + + "The object created\/updated\/deleted by the mutation. May be null if mutation failed." + result: OrganizationTagConstraint +} + +"The role of a group within an organization." +enum GroupRole { + "Full administrative access to the organization" + ORGANIZATION_ADMIN + + "Read-only access to the organization" + ORGANIZATION_VIEWER + + "Custom role with project-level access grants" + CUSTOM +} + +"Available fields for sorting groups." +enum GroupsSortField { + "Sort alphabetically by group name" + NAME + + "Sort by when the group was created" + CREATED_AT +} + +"Sorting options for the groups list." +input GroupsSort { + "Which field to sort by" + field: GroupsSortField! + + "Sort direction" + order: SortOrder! +} + +"Create a new group. Groups control which projects members can access." +input CreateGroupInput { + "What this group is for" + description: String + + "A human-readable name for the group" + name: String! +} + +"Update a group's name or description." +input UpdateGroupInput { + "What this group is for" + description: String + + "A human-readable name for the group" + name: String! +} + +"A group." +type Group { + "Group identifier" + id: ID! + + "Group name" + name: String! + + "What this group is for" + description: String + + "Access level within the organization" + role: GroupRole! + + "When this group was created (UTC)" + createdAt: DateTime! + + "When this group was last modified (UTC)" + updatedAt: DateTime! +} + +"An invitation to join a group." +type GroupInvitation { + "Invitation identifier" + id: ID! + + "Email address of the invited user" + email: String! + + "When the invitation was sent (UTC)" + createdAt: DateTime! +} + +"A group member." +type GroupMember { + "Email address of the member" + email: String! +} + +type GroupsPage { + "Pagination cursors" + cursor: PaginationCursor! + + "A list of type group." + items: [Group] +} + +type GroupPayload { + "Indicates if the mutation completed successfully or not." + successful: Boolean! + + "A list of failed validations. May be blank or null if mutation succeeded." + messages: [ValidationMessage] + + "The object created\/updated\/deleted by the mutation. May be null if mutation failed." + result: Group +} + +type GroupInvitationPayload { + "Indicates if the mutation completed successfully or not." + successful: Boolean! + + "A list of failed validations. May be blank or null if mutation succeeded." + messages: [ValidationMessage] + + "The object created\/updated\/deleted by the mutation. May be null if mutation failed." + result: GroupInvitation +} + +type GroupMemberPayload { + "Indicates if the mutation completed successfully or not." + successful: Boolean! + + "A list of failed validations. May be blank or null if mutation succeeded." + messages: [ValidationMessage] + + "The object created\/updated\/deleted by the mutation. May be null if mutation failed." + result: GroupMember +} + +"Create a new organization. An organization is the top-level container for all your projects, environments, and infrastructure resources." +input CreateOrganizationInput { + "A short, memorable identifier for looking up this organization in the API and CLI. Choose something concise and meaningful—human-readable, not a UUID. Max 20 characters, lowercase alphanumeric only (a-z, 0-9). Immutable after creation." + id: String! + + "A human-readable name for the organization" + name: String! +} + +"An organization." +type Organization { + id: ID! + + "Organization name" + name: String! + + "When this organization was created (UTC)" + createdAt: DateTime! + + "When this organization was last modified (UTC)" + updatedAt: DateTime! + + "The organization's logo image." + logo: LogoOrganization + + "Tag constraints that define the structural tags for this organization." + tagConstraints( + "How to sort results" + sort: OrganizationTagConstraintsSort + + "Pagination cursor from a previous response" + cursor: Cursor + ): OrganizationTagConstraintsPage + + "JSON Schema describing the tag fields for a given resource scope." + tagSchema( + "Resource level (e.g. PROJECT, ENVIRONMENT)" + scope: TagConstraintScope! + ): Map! +} + +"A deleted organization member." +type DeletedOrganizationMember { + "Email address of the removed member" + email: String! +} + +type OrganizationPayload { + "Indicates if the mutation completed successfully or not." + successful: Boolean! + + "A list of failed validations. May be blank or null if mutation succeeded." + messages: [ValidationMessage] + + "The object created\/updated\/deleted by the mutation. May be null if mutation failed." + result: Organization +} + +type DeletedOrganizationMemberPayload { + "Indicates if the mutation completed successfully or not." + successful: Boolean! + + "A list of failed validations. May be blank or null if mutation succeeded." + messages: [ValidationMessage] + + "The object created\/updated\/deleted by the mutation. May be null if mutation failed." + result: DeletedOrganizationMember +} + +"An organization's logo image." +type LogoOrganization { + id: ID! + + "URL to the logo image. Uses the organization's internal ID for privacy." + url: String! + + "MIME type (e.g., image\/png)" + contentType: String! + + "File size in bytes" + fileSize: Int! + + "Image width in pixels" + width: Int + + "Image height in pixels" + height: Int + + "When the logo was uploaded (UTC)" + createdAt: DateTime! + + "When the logo was last changed (UTC)" + updatedAt: DateTime! +} + +type LogoOrganizationPayload { + "Indicates if the mutation completed successfully or not." + successful: Boolean! + + "A list of failed validations. May be blank or null if mutation succeeded." + messages: [ValidationMessage] + + "The object created\/updated\/deleted by the mutation. May be null if mutation failed." + result: LogoOrganization +} + +"Available fields for sorting organizations." +enum ViewerOrganizationsSortField { + "Sort alphabetically by organization name" + NAME + + "Sort by when you joined the organization" + CREATED_AT +} + +"Billing status of an organization." +enum ViewerBillingStatus { + "Free trial period" + TRIAL + + "Subscription is active and paid" + ACTIVE + + "Payment is overdue" + PAST_DUE + + "Trial has expired" + EXPIRED + + "Subscription was canceled" + CANCELED + + "Account is temporarily suspended" + SUSPENDED + + "Waiting for payment to process" + PAYMENT_PENDING + + "Payment failed" + PAYMENT_FAILED +} + +"Available fields for sorting identities." +enum IdentitiesSortField { + "Sort by provider name" + PROVIDER + + "Sort by when the identity was linked" + CREATED_AT +} + +"Available fields for sorting invites." +enum InvitesSortField { + "Sort by when the invite was sent" + CREATED_AT +} + +"Type of group." +enum ViewerGroupType { + "Organization administrators" + ORGANIZATION_ADMIN + + "Organization viewers" + ORGANIZATION_VIEWER + + "Custom group with specific permissions" + CUSTOM +} + +"Sorting options for the organizations list." +input ViewerOrganizationsSort { + "Which field to sort by" + field: ViewerOrganizationsSortField! + + "Sort direction" + order: SortOrder! +} + +"Sorting options for the identities list." +input IdentitiesSort { + "Which field to sort by" + field: IdentitiesSortField! + + "Sort direction" + order: SortOrder! +} + +"Sorting options for the invites list." +input InvitesSort { + "Which field to sort by" + field: InvitesSortField! + + "Sort direction" + order: SortOrder! +} + +"Billing information for an organization." +type ViewerOrganizationBilling { + "Current billing status" + status: ViewerBillingStatus! +} + +"An organization you belong to." +type ViewerOrganization { + id: ID! + + "Organization name" + name: String! + + "Billing status for this organization." + billing: ViewerOrganizationBilling +} + +type ViewerOrganizationsPage { + "Pagination cursors" + cursor: PaginationCursor! + + "A list of type viewer_organization." + items: [ViewerOrganization] +} + +"An OAuth\/OIDC provider identity linked to your account." +type AccountIdentityViewer { + id: ID! + + "OAuth provider (e.g., 'google', 'github')" + provider: String! + + "When this identity was linked (UTC)" + createdAt: DateTime! +} + +type IdentitiesViewerPage { + "Pagination cursors" + cursor: PaginationCursor! + + "A list of type account_identity_viewer." + items: [AccountIdentityViewer] +} + +"Organization info from an invitation." +type InviteOrganizationViewer { + id: ID! + + "Organization name" + name: String! +} + +"Group info from an invitation." +type InviteGroupViewer { + id: ID! + + "Group name" + name: String! + + "Type of group." + type: ViewerGroupType! + + "The organization this group belongs to." + organization: InviteOrganizationViewer! +} + +"A pending group membership invitation." +type InviteViewer { + id: ID! + + "Email the invitation was sent to" + email: String! + + "When the invitation was sent (UTC)" + createdAt: DateTime! + + "The group you've been invited to join." + group: InviteGroupViewer! +} + +type InvitesViewerPage { + "Pagination cursors" + cursor: PaginationCursor! + + "A list of type invite_viewer." + items: [InviteViewer] +} + +"Your profile avatar image." +type AvatarViewer { + id: ID! + + "URL to the avatar image." + url: String! + + "MIME type (e.g., image\/png)" + contentType: String! + + "File size in bytes" + fileSize: Int! + + "Image width in pixels" + width: Int + + "Image height in pixels" + height: Int + + "When the avatar was uploaded (UTC)" + createdAt: DateTime! + + "When the avatar was last changed (UTC)" + updatedAt: DateTime! +} + +type InviteViewerPayload { + "Indicates if the mutation completed successfully or not." + successful: Boolean! + + "A list of failed validations. May be blank or null if mutation succeeded." + messages: [ValidationMessage] + + "The object created\/updated\/deleted by the mutation. May be null if mutation failed." + result: InviteViewer +} + +type AvatarViewerPayload { + "Indicates if the mutation completed successfully or not." + successful: Boolean! + + "A list of failed validations. May be blank or null if mutation succeeded." + messages: [ValidationMessage] + + "The object created\/updated\/deleted by the mutation. May be null if mutation failed." + result: AvatarViewer +} + +"Information about the authenticated user." +type AccountViewer { + id: ID! + + "Email address" + email: String! + + "First name" + firstName: String + + "Last name" + lastName: String + + "When you signed up (UTC)" + createdAt: DateTime! + + "When your profile was last updated (UTC)" + updatedAt: DateTime! + + "Your profile avatar." + avatar: AvatarViewer + + "Organizations you belong to." + organizations( + "How to sort results" + sort: ViewerOrganizationsSort + + "Pagination cursor from a previous response" + cursor: Cursor + ): ViewerOrganizationsPage + + "Your most recently joined organization. Useful for setting a default context." + defaultOrganization: ViewerOrganization + + "OAuth\/OIDC identities linked to your account." + identities( + "How to sort" + sort: IdentitiesSort + + "Pagination cursor" + cursor: Cursor + ): IdentitiesViewerPage + + "Pending group membership invitations." + invites( + "How to sort" + sort: InvitesSort + + "Pagination cursor" + cursor: Cursor + ): InvitesViewerPage +} + +"Information about the authenticated service account (API client)." +type ServiceAccountViewer { + id: ID! + + "Service account name" + name: String! + + "What this service account is used for" + description: String + + "When this service account was created (UTC)" + createdAt: DateTime! + + "When this service account was last modified (UTC)" + updatedAt: DateTime! + + "The organization this service account belongs to." + organization: ViewerOrganization! +} + +"The authenticated entity making this request. Returns AccountViewer for users, ServiceAccountViewer for API clients." +union Viewer = AccountViewer | ServiceAccountViewer + +"Available fields for sorting components." +enum ComponentsSortField { + "Sort alphabetically by component name" + NAME + + "Sort by when the component was created" + CREATED_AT +} + +"Available fields for sorting links." +enum LinksSortField { + "Sort by when the link was created" + CREATED_AT +} + +"Sorting options for the components list." +input ComponentsSort { + "Which field to sort by" + field: ComponentsSortField! + + "Sort direction" + order: SortOrder! +} + +"Sorting options for the links list." +input LinksSort { + "Which field to sort by" + field: LinksSortField! + + "Sort direction" + order: SortOrder! +} + +"Filter which components to return." +input ComponentsFilter { + "Filter by component ID (e.g., 'database')" + id: IdFilter + + "Filter by OCI repository name" + ociRepoName: OciRepoNameFilter +} + +"Filter which links to return." +input LinksFilter { + "Filter by source (from) component ID" + fromComponentId: IdFilter + + "Filter by destination (to) component ID" + toComponentId: IdFilter +} + +"Add an infrastructure component to a project's blueprint. Each component is a specific instance of a bundle (like a Redis cache or PostgreSQL database) that composes with other components to form your application." +input AddComponentInput { + "Optional description of this component's purpose" + description: String + + "A short, memorable identifier for this component. This becomes the final segment of package identifiers. For example, project 'ecomm' with environment 'prod' and component 'db' creates 'ecomm-prod-db'. Max 20 characters, lowercase alphanumeric only (a-z, 0-9). Immutable after creation." + id: String! + + "Display name for this component (e.g., 'Billing Database')" + name: String! + + "Name of the OCI repository for the bundle to add (e.g., 'aws-aurora-postgres')" + ociRepoName: OciRepoName! + + "Key-value tags for this component. Keys and values must be strings. Must conform to the organization's tag constraints for the component scope." + tags: Map +} + +"Create a link between two components in a project's blueprint. Links connect an output field on the source component to an input field on the destination component, establishing data flow between infrastructure resources." +input LinkComponentsInput { + "Component that produces the artifact (e.g., 'database')" + from: ID! + + "Output field name on the source component" + fromField: String! + + "Version constraint for the source component (e.g., '~1.0', '1.2.3', 'latest')" + fromVersion: VersionConstraint! + + "Component that consumes the artifact (e.g., 'app')" + to: ID! + + "Input field name on the destination component" + toField: String! + + "Version constraint for the destination component (e.g., '~1.0', '1.2.3', 'latest')" + toVersion: VersionConstraint! +} + +"Set the position of a component on the canvas." +input SetComponentPositionInput { + "Horizontal position in pixels" + x: Int! + + "Vertical position in pixels" + y: Int! +} + +"A component's position on the canvas." +type ComponentPosition { + "Horizontal position in pixels" + x: Int! + + "Vertical position in pixels" + y: Int! +} + +"A component." +type Component { + id: ID! + + "Display name" + name: String! + + "What this component is for" + description: String + + "Tags assigned directly to this component" + tags: Map! + + "Position on the canvas" + position: ComponentPosition + + "When this component was created (UTC)" + createdAt: DateTime! + + "When this component was last modified (UTC)" + updatedAt: DateTime! + + "The OCI repository this component uses." + ociRepo: OciRepo + + "The project containing this component." + project: Project +} + +"A link." +type Link { + id: ID! + + "Output field name on the source component" + fromField: String! + + "Input field name on the destination component" + toField: String! + + "When this link was created (UTC)" + createdAt: DateTime! + + "When this link was last modified (UTC)" + updatedAt: DateTime! + + "The source (from) component." + fromComponent: Component + + "The destination (to) component." + toComponent: Component +} + +""" +A project's infrastructure blueprint. + +The blueprint contains all components (bundle instances) and links (connections +between them) that define how your infrastructure fits together. +""" +type Blueprint { + "Components in this blueprint." + components( + "Filter results" + filter: ComponentsFilter + + "How to sort results" + sort: ComponentsSort + + "Pagination cursor from a previous response" + cursor: Cursor + ): ComponentsPage + + "Links between components in this blueprint." + links( + "Filter results" + filter: LinksFilter + + "How to sort results" + sort: LinksSort + + "Pagination cursor from a previous response" + cursor: Cursor + ): LinksPage +} + +type ComponentsPage { + "Pagination cursors" + cursor: PaginationCursor! + + "A list of type component." + items: [Component] +} + +type LinksPage { + "Pagination cursors" + cursor: PaginationCursor! + + "A list of type link." + items: [Link] +} + +type ComponentPayload { + "Indicates if the mutation completed successfully or not." + successful: Boolean! + + "A list of failed validations. May be blank or null if mutation succeeded." + messages: [ValidationMessage] + + "The object created\/updated\/deleted by the mutation. May be null if mutation failed." + result: Component +} + +type LinkPayload { + "Indicates if the mutation completed successfully or not." + successful: Boolean! + + "A list of failed validations. May be blank or null if mutation succeeded." + messages: [ValidationMessage] + + "The object created\/updated\/deleted by the mutation. May be null if mutation failed." + result: Link +} + +"The current state of a deployment operation." +enum DeploymentStatus { + "Waiting to start" + PENDING + + "Currently executing" + RUNNING + + "Finished successfully" + COMPLETED + + "Failed - check logs for details" + FAILED + + "Manually stopped before completion" + ABORTED +} + +"The type of infrastructure operation." +enum DeploymentAction { + "Create or update infrastructure" + PROVISION + + "Tear down infrastructure" + DECOMMISSION + + "Preview changes without applying them" + PLAN +} + +"Available fields for sorting deployments." +enum DeploymentsSortField { + "Sort by when the deployment started" + CREATED_AT + + "Sort by deployment status" + STATUS +} + +"Sorting options for the deployments list." +input DeploymentsSort { + "Which field to sort by" + field: DeploymentsSortField! + + "Sort direction" + order: SortOrder! +} + +"Filter which deployments to return." +input DeploymentsFilter { + "Filter by instance ID" + instanceId: IdFilter + + "Filter by deployment status" + status: DeploymentStatusFilter + + "Filter by deployment action" + action: DeploymentActionFilter +} + +"A deployment." +type Deployment { + id: ID! + + "Current state of the deployment" + status: DeploymentStatus! + + "Type of operation" + action: DeploymentAction! + + "Bundle version being deployed" + version: String! + + "Deployment message or commit info" + message: String + + "When this deployment started (UTC)" + createdAt: DateTime! + + "When this deployment was last updated (UTC)" + updatedAt: DateTime! + + "When the status last changed (UTC)" + lastTransitionedAt: DateTime + + "How long the deployment has been running, in seconds." + elapsedTime: Int! + + "Who started this deployment." + deployedBy: String + + "The instance being deployed." + instance: Instance +} + +type DeploymentsPage { + "Pagination cursors" + cursor: PaginationCursor! + + "A list of type deployment." + items: [Deployment] +} + +"Available fields for sorting instances." +enum InstancesSortField { + "Sort alphabetically by instance name" + NAME + + "Sort by when the instance was created" + CREATED_AT +} + +"The current state of an instance." +enum InstanceStatus { + "Created but not yet deployed" + INITIALIZED + + "Successfully deployed and running" + PROVISIONED + + "Infrastructure has been torn down" + DECOMMISSIONED + + "Deployment failed - check the deployment logs" + FAILED + + "Imported infrastructure managed outside Massdriver" + EXTERNAL +} + +"Which bundle releases an instance will use." +enum ReleaseStrategy { + "Only use stable, published releases" + STABLE + + "Use development releases for testing" + DEVELOPMENT +} + +"Available fields for sorting param dimensions." +enum ParamDimensionsSortField { + "Sort by field path" + FIELD + + "Sort by label" + LABEL +} + +"A configuration field available in instance params. Use these to build search filters." +type ParamDimension { + "Path to the field (e.g., 'database.instance_type')" + field: String! + + "Human-readable field name" + label: String! + + "What this field configures" + description: String + + "Data type (string, number, boolean, etc.)" + type: String +} + +"Sorting options for the instances list." +input InstancesSort { + "Which field to sort by" + field: InstancesSortField! + + "Sort direction" + order: SortOrder! +} + +"Filter which instances to return." +input InstancesFilter { + "Filter by project ID" + projectId: IdFilter + + "Filter by environment ID" + environmentId: IdFilter + + "Filter by instance status" + status: InstanceStatusFilter + + "Filter by OCI repository name" + ociRepoName: OciRepoNameFilter + + "Filter by param dimension values. Multiple filters are combined with AND." + paramDimension: [ParamDimensionFilter!] +} + +"Filter which param dimensions to return. Scopes which instances' schemas are introspected." +input ParamDimensionsFilter { + "Only include dimensions from instances in these projects" + projectId: IdFilter + + "Only include dimensions from instances in these environments" + environmentId: IdFilter + + "Only include dimensions from instances using these bundles" + ociRepoName: OciRepoNameFilter +} + +"Sorting options for the param dimensions list." +input ParamDimensionsSort { + "Which field to sort by" + field: ParamDimensionsSortField! + + "Sort direction" + order: SortOrder! +} + +"An artifact produced or consumed by an instance." +type Resource { + "Resource identifier (`package.field` for provisioned resources, UUID for imports)" + id: ID! + + "Resource name" + name: String! + + "Resource type (e.g., the artifact definition type)" + type: String + + "When this resource was created (UTC)" + createdAt: DateTime! + + "When this resource was last modified (UTC)" + updatedAt: DateTime! +} + +"A resource keyed by the output handle that produced it." +type InstanceResource { + "The output handle that produced this resource" + field: String! + + "The resource artifact" + resource: Resource! +} + +"A dependency keyed by the input handle that consumes it." +type InstanceDependency { + "The input handle that consumes this resource" + field: String! + + "The resource artifact" + resource: Resource! +} + +type InstanceResourcesPage { + "Pagination cursors" + cursor: PaginationCursor! + + "A list of type instance_resource." + items: [InstanceResource] +} + +type InstanceDependenciesPage { + "Pagination cursors" + cursor: PaginationCursor! + + "A list of type instance_dependency." + items: [InstanceDependency] +} + +"An instance." +type Instance { + id: ID! + + "Display name" + name: String! + + "Current state of the instance" + status: InstanceStatus! + + "Tags assigned directly to this instance" + tags: Map! + + "Version constraint (e.g., '~1.0' or '1.2.3')" + version: String! + + "Whether to use stable or development releases" + releaseStrategy: ReleaseStrategy! + + "When this instance was created (UTC)" + createdAt: DateTime! + + "When this instance was last modified (UTC)" + updatedAt: DateTime! + + "The actual version resolved from the version constraint, used for the next deployment." + resolvedVersion: String! + + "The version last deployed to infrastructure. May differ from resolvedVersion if not yet deployed." + deployedVersion: String + + "Newest version available for upgrade, or null if already on the latest matching version." + availableUpgrade: String + + "Cloud provider costs for this instance." + cost: CostSummary! + + "The environment this instance is deployed in." + environment: Environment + + "The bundle version currently deployed." + bundle: Bundle + + "Resources produced by this instance (its output artifacts)." + resources(cursor: Cursor): InstanceResourcesPage + + "Dependencies consumed by this instance (artifacts from other instances wired via connections)." + dependencies(cursor: Cursor): InstanceDependenciesPage +} + +type InstancesPage { + "Pagination cursors" + cursor: PaginationCursor! + + "A list of type instance." + items: [Instance] +} + +type ParamDimensionsPage { + "Pagination cursors" + cursor: PaginationCursor! + + "A list of type param_dimension." + items: [ParamDimension] +} + +"Available fields for sorting connections." +enum ConnectionsSortField { + "Sort by when the connection was created" + CREATED_AT +} + +"Sorting options for the connections list." +input ConnectionsSort { + "Which field to sort by" + field: ConnectionsSortField! + + "Sort direction" + order: SortOrder! +} + +"Filter which connections to return." +input ConnectionsFilter { + "Filter by source (from) instance ID" + fromInstanceId: IdFilter + + "Filter by destination (to) instance ID" + toInstanceId: IdFilter +} + +"A connection between two instances in an environment, representing the runtime wiring of a blueprint link." +type Connection { + id: ID! + + "Output field name on the source instance" + fromField: String! + + "Input field name on the destination instance" + toField: String! + + "When this connection was created (UTC)" + createdAt: DateTime! + + "When this connection was last modified (UTC)" + updatedAt: DateTime! + + "The source (from) instance that produces the resource." + fromInstance: Instance + + "The destination (to) instance that consumes the resource." + toInstance: Instance + + "The blueprint link this connection instantiates." + link: Link +} + +""" +An environment's realized infrastructure blueprint. + +The environment blueprint contains all instances (deployed packages) and +connections (runtime wiring between them) showing how your infrastructure +is actually deployed. This mirrors the project-level blueprint at the +environment level. +""" +type EnvironmentBlueprint { + "Instances deployed in this environment." + instances( + "Filter results" + filter: InstancesFilter + + "How to sort results" + sort: InstancesSort + + "Pagination cursor from a previous response" + cursor: Cursor + ): InstancesPage + + "Connections between instances in this environment." + connections( + "Filter results" + filter: ConnectionsFilter + + "How to sort results" + sort: ConnectionsSort + + "Pagination cursor from a previous response" + cursor: Cursor + ): ConnectionsPage +} + +type ConnectionsPage { + "Pagination cursors" + cursor: PaginationCursor! + + "A list of type connection." + items: [Connection] +} + +"Available fields for sorting environments." +enum EnvironmentsSortField { + "Sort alphabetically by environment name" + NAME + + "Sort by when the environment was created" + CREATED_AT +} + +"Sorting options for the environments list." +input EnvironmentsSort { + "Which field to sort by" + field: EnvironmentsSortField! + + "Sort direction" + order: SortOrder! +} + +"Filter which environments to return." +input EnvironmentsFilter { + "Filter by project ID" + projectId: IdFilter + + "Filter by environment ID" + id: StringFilter +} + +"Create a new environment. Environments are isolated deployment contexts like production, staging, or development, each with independent secrets and configurations." +input CreateEnvironmentInput { + "An optional description of the environment's purpose" + description: String + + "A short, memorable identifier for looking up this environment in the API and CLI. This becomes the second segment of package identifiers. For example, project 'ecomm' with environment 'prod' and component 'db' creates 'ecomm-prod-db'. Use familiar names like 'prod', 'staging', 'dev'—human-readable, not a UUID. Max 20 characters, lowercase alphanumeric only (a-z, 0-9). Immutable after creation." + id: String! + + "A human-readable name for the environment" + name: String! + + "Key-value tags for this environment. Keys and values must be strings. Must conform to the organization's tag constraints for the environment scope." + tags: Map +} + +"Update an existing environment's name and description. The ID cannot be changed after creation." +input UpdateEnvironmentInput { + "An optional description of the environment's purpose" + description: String + + "A human-readable name for the environment" + name: String + + "Key-value tags for this environment. Keys and values must be strings. Must conform to the organization's tag constraints for the environment scope." + tags: Map +} + +"An environment." +type Environment { + id: ID! + + "Display name" + name: String! + + "What this environment is for" + description: String + + "Tags assigned directly to this environment" + tags: Map! + + "When this environment was created (UTC)" + createdAt: DateTime! + + "When this environment was last modified (UTC)" + updatedAt: DateTime! + + "The project containing this environment." + project: Project + + "Cloud provider costs for this environment." + cost: CostSummary! + + "Whether this environment can be deleted and any constraints preventing deletion." + deletable: Deletable! + + "The realized infrastructure blueprint for this environment (instances and connections)." + blueprint: EnvironmentBlueprint +} + +type EnvironmentsPage { + "Pagination cursors" + cursor: PaginationCursor! + + "A list of type environment." + items: [Environment] +} + +type EnvironmentPayload { + "Indicates if the mutation completed successfully or not." + successful: Boolean! + + "A list of failed validations. May be blank or null if mutation succeeded." + messages: [ValidationMessage] + + "The object created\/updated\/deleted by the mutation. May be null if mutation failed." + result: Environment +} + +"Available fields for sorting projects." +enum ProjectsSortField { + "Sort alphabetically by project name" + NAME + + "Sort by when the project was created" + CREATED_AT +} + +"Sorting options for the projects list." +input ProjectsSort { + "Which field to sort by" + field: ProjectsSortField! + + "Sort direction" + order: SortOrder! +} + +"Create a new project. A project is the complete model of your application—its infrastructure, architecture, configurations, and environments." +input CreateProjectInput { + "An optional description of the project's purpose or contents" + description: String + + "A short, memorable identifier for looking up this project in the API and CLI. This becomes the first segment of all resource identifiers within the project. For example, a project 'ecomm' with environment 'prod' and component 'db' creates the package identifier 'ecomm-prod-db'. Choose something concise and meaningful—human-readable, not a UUID. Max 20 characters, lowercase alphanumeric only (a-z, 0-9). Immutable after creation." + id: String! + + "A human-readable name for the project" + name: String! + + "Key-value tags for this project. Keys and values must be strings. Must conform to the organization's tag constraints for the project scope." + tags: Map +} + +"Update an existing project's name and description. The ID cannot be changed after creation." +input UpdateProjectInput { + "An optional description of the project's purpose or contents" + description: String + + "A human-readable name for the project" + name: String + + "Key-value tags for this project. Keys and values must be strings. Must conform to the organization's tag constraints for the project scope." + tags: Map +} + +"A project." +type Project { + id: ID! + + "Display name" + name: String! + + "What this project is for" + description: String + + "Tags assigned directly to this project" + tags: Map! + + "When this project was created (UTC)" + createdAt: DateTime! + + "When this project was last modified (UTC)" + updatedAt: DateTime! + + "Whether this project can be deleted and any constraints preventing deletion." + deletable: Deletable! + + "Cloud provider costs for this project." + cost: CostSummary! + + "The environments in this project, like staging or production." + environments(sort: EnvironmentsSort, cursor: Cursor): EnvironmentsPage + + "The infrastructure blueprint for this project (components and links)." + blueprint: Blueprint +} + +type ProjectsPage { + "Pagination cursors" + cursor: PaginationCursor! + + "A list of type project." + items: [Project] +} + +type ProjectPayload { + "Indicates if the mutation completed successfully or not." + successful: Boolean! + + "A list of failed validations. May be blank or null if mutation succeeded." + messages: [ValidationMessage] + + "The object created\/updated\/deleted by the mutation. May be null if mutation failed." + result: Project +} + +""" +Semantic version (e.g., `1.2.3` or `1.2.3-dev.20060102T150405Z`). + +Always a fully resolved version, never a constraint or channel. +""" +scalar Semver + +""" +Bundle identifier in `name@version` format. + +Returns the fully resolved version of a bundle (e.g., `aws-aurora-postgres@1.2.3` +or `aws-aurora-postgres@1.2.3-dev.20060102T150405Z`). + +As an input, accepts: +- `aws-aurora-postgres` — bare name, resolves to latest +- `aws-aurora-postgres@1.2.3` — exact version +- `aws-aurora-postgres@~1.2` — release channel (resolved server-side) +- `aws-aurora-postgres@latest` — latest stable release +- `aws-aurora-postgres@latest+dev` — latest including dev builds +""" +scalar BundleId + +""" +Version constraint for bundle releases. + +Accepts exact versions or release channels: +- `1.2.3` — exact version +- `1.2.3-dev.20060102T150405Z` — exact dev release +- `~1.2` — latest patch in 1.2.x series +- `~1` — latest minor in 1.x.x series +- `~1.2+dev` — latest in 1.2.x including dev builds +- `latest` — newest stable release +- `latest+dev` — newest release including dev builds +""" +scalar VersionConstraint + +"A bundle." +type Bundle { + "Bundle identifier in `name@version` format (e.g., 'aws-aurora-postgres@1.2.3')" + id: BundleId! + + "Bundle name (e.g., 'aws-aurora-postgres')" + name: OciRepoName! + + "Resolved semantic version (e.g., '1.2.3')" + version: Semver! + + "What this bundle provides" + description: String + + "URL to the bundle icon" + icon: String + + "URL to the bundle source code" + sourceUrl: String + + "When this bundle version was published (UTC)" + createdAt: DateTime! + + "When this bundle version was last modified (UTC)" + updatedAt: DateTime! + + "OCI repository path for this bundle (e.g., 'aws-aurora-postgres')" + repo: String! +} + +""" +OCI repository name (e.g., 'aws-aurora-postgres'). + +The short name of an OCI repository in your organization's catalog. +""" +scalar OciRepoName + +"An OCI repository." +type OciRepo { + "OCI repository name" + name: OciRepoName! +} + +"A single cost measurement with amount and currency." +type CostSample { + "The cost amount (null if no data available)" + amount: Float + + "The currency code, e.g. USD (null if no data available)" + currency: String +} + +"Cost summary with monthly and daily metrics." +type CostSummary { + "Previous month's total cost" + lastMonth: CostSample! + + "Average monthly cost" + monthlyAverage: CostSample! + + "Previous day's cost" + lastDay: CostSample! + + "Average daily cost" + dailyAverage: CostSample! +} + +"A constraint preventing a resource from being deleted." +type DeletionConstraint { + "Human-readable explanation of why deletion is blocked" + message: String! + + "The type of resource that cannot be deleted (e.g., 'project', 'environment')" + type: String! + + "The identifier of the resource that cannot be deleted" + id: String! +} + +"Indicates whether a resource can be deleted and any constraints preventing deletion." +type Deletable { + "Whether the resource can be deleted" + result: Boolean! + + "Constraints preventing deletion, if any" + constraints: [DeletionConstraint] +} + +"Sort direction for paginated results." +enum SortOrder { + "Ascending order (A→Z, oldest→newest)" + ASC + + "Descending order (Z→A, newest→oldest)" + DESC +} + +"Filter for ID fields. All provided operators are combined with AND." +input IdFilter { + "Exact match" + eq: ID + + "Match any of these values" + in: [ID!] +} + +"Filter for string fields. All provided operators are combined with AND." +input StringFilter { + "Exact match" + eq: String + + "Match any of these values" + in: [String!] +} + +"Filter by OCI repository name." +input OciRepoNameFilter { + "Exact match" + eq: OciRepoName + + "Match any of these values" + in: [OciRepoName!] +} + +""" +Filter for datetime fields. All provided operators are combined with AND. +Useful for date range queries. +""" +input DatetimeFilter { + "Exact match" + eq: DateTime + + "After this date\/time" + gt: DateTime + + "On or after this date\/time" + gte: DateTime + + "Before this date\/time" + lt: DateTime + + "On or before this date\/time" + lte: DateTime +} + +"Filter for instance status. All provided operators are combined with AND." +input InstanceStatusFilter { + "Exact match" + eq: InstanceStatus + + "Match any of these statuses" + in: [InstanceStatus!] +} + +"Filter for deployment status. All provided operators are combined with AND." +input DeploymentStatusFilter { + "Exact match" + eq: DeploymentStatus + + "Match any of these statuses" + in: [DeploymentStatus!] +} + +"Filter for deployment action. All provided operators are combined with AND." +input DeploymentActionFilter { + "Exact match" + eq: DeploymentAction + + "Match any of these actions" + in: [DeploymentAction!] +} + +""" +Filter instances by a configuration parameter value at a specific path. +Use `paramDimensions` query to discover available dimensions. +""" +input ParamDimensionFilter { + "Dot-path to the param field (e.g., 'database.instance_type')" + dimension: String! + + "Exact match" + eq: String + + "Match any of these values" + in: [String!] + + "Case-insensitive substring match" + contains: String +} + +input Cursor { + "Maximum number of items per page. Default: 20. Minimum: 1. Maximum: 100." + limit: Int + + "Cursor after which to return items" + next: String + + "Cursor before which to return items" + previous: String +} + +type PaginationCursor { + "Cursor to the next page" + next: String + + "Cursor to the previous page" + previous: String +} + +"A key-value map. Does not accept arrays, strings, or other JSON primitives." +scalar Map + +type ValidationOption { + "The name of a variable to be subsituted in a validation message template" + key: String! + + "The value of a variable to be substituted in a validation message template" + value: String! +} + +""" +Validation messages are returned when mutation input does not meet the requirements. + While client-side validation is highly recommended to provide the best User Experience, + All inputs will always be validated server-side. + + Some examples of validations are: + + * Username must be at least 10 characters + * Email field does not contain an email address + * Birth Date is required + + While GraphQL has support for required values, mutation data fields are always + set to optional in our API. This allows 'required field' messages + to be returned in the same manner as other validations. The only exceptions + are id fields, which may be required to perform updates or deletes. +""" +type ValidationMessage { + """ + The input field that the error applies to. The field can be used to + identify which field the error message should be displayed next to in the + presentation layer. + + If there are multiple errors to display for a field, multiple validation + messages will be in the result. + + This field may be null in cases where an error cannot be applied to a specific field. + """ + field: String + + """ + A friendly error message, appropriate for display to the end user. + + The message is interpolated to include the appropriate variables. + + Example: `Username must be at least 10 characters` + + This message may change without notice, so we do not recommend you match against the text. + Instead, use the *code* field for matching. + """ + message: String + + "A unique error code for the type of validation used." + code: String! + + """ + A template used to generate the error message, with placeholders for option substiution. + + Example: `Username must be at least {count} characters` + + This message may change without notice, so we do not recommend you match against the text. + Instead, use the *code* field for matching. + """ + template: String + + "A list of substitutions to be applied to a validation message template" + options: [ValidationOption] +} + +"Represents an uploaded file." +scalar Upload + +""" +The `DateTime` scalar type represents a date and time in the UTC +timezone. The DateTime appears in a JSON response as an ISO8601 formatted +string, including UTC timezone ("Z"). The parsed date and time string will +be converted to UTC if there is an offset. +""" +scalar DateTime diff --git a/internal/api/v1/tools.go b/internal/api/v1/tools.go new file mode 100644 index 00000000..e861d187 --- /dev/null +++ b/internal/api/v1/tools.go @@ -0,0 +1,9 @@ +//go:build tools + +package api + +import ( + // Keeps genqlient CLI dependencies (alexflint/go-arg, alexflint/go-scalar, + // agnivade/levenshtein, etc.) from being pruned by `go mod tidy`. + _ "github.com/Khan/genqlient/generate" +) diff --git a/internal/api/v1/zz_generated.go b/internal/api/v1/zz_generated.go new file mode 100644 index 00000000..27c5fb25 --- /dev/null +++ b/internal/api/v1/zz_generated.go @@ -0,0 +1,2727 @@ +// Code generated by github.com/Khan/genqlient, DO NOT EDIT. + +package api + +import ( + "context" + "encoding/json" + "fmt" + "time" + + "github.com/Khan/genqlient/graphql" + "github.com/massdriver-cloud/mass/internal/api/v1/scalars" +) + +// Create a new environment. Environments are isolated deployment contexts like production, staging, or development, each with independent secrets and configurations. +type CreateEnvironmentInput struct { + // An optional description of the environment's purpose + Description string `json:"description"` + // A short, memorable identifier for looking up this environment in the API and CLI. This becomes the second segment of package identifiers. For example, project 'ecomm' with environment 'prod' and component 'db' creates 'ecomm-prod-db'. Use familiar names like 'prod', 'staging', 'dev'—human-readable, not a UUID. Max 20 characters, lowercase alphanumeric only (a-z, 0-9). Immutable after creation. + Id string `json:"id"` + // A human-readable name for the environment + Name string `json:"name"` + // Key-value tags for this environment. Keys and values must be strings. Must conform to the organization's tag constraints for the environment scope. + Tags map[string]any `json:"-"` +} + +// GetDescription returns CreateEnvironmentInput.Description, and is useful for accessing the field via an interface. +func (v *CreateEnvironmentInput) GetDescription() string { return v.Description } + +// GetId returns CreateEnvironmentInput.Id, and is useful for accessing the field via an interface. +func (v *CreateEnvironmentInput) GetId() string { return v.Id } + +// GetName returns CreateEnvironmentInput.Name, and is useful for accessing the field via an interface. +func (v *CreateEnvironmentInput) GetName() string { return v.Name } + +// GetTags returns CreateEnvironmentInput.Tags, and is useful for accessing the field via an interface. +func (v *CreateEnvironmentInput) GetTags() map[string]any { return v.Tags } + +func (v *CreateEnvironmentInput) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *CreateEnvironmentInput + Tags json.RawMessage `json:"tags"` + graphql.NoUnmarshalJSON + } + firstPass.CreateEnvironmentInput = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + { + dst := &v.Tags + src := firstPass.Tags + if len(src) != 0 && string(src) != "null" { + err = scalars.UnmarshalJSON( + src, dst) + if err != nil { + return fmt.Errorf( + "unable to unmarshal CreateEnvironmentInput.Tags: %w", err) + } + } + } + return nil +} + +type __premarshalCreateEnvironmentInput struct { + Description string `json:"description"` + + Id string `json:"id"` + + Name string `json:"name"` + + Tags json.RawMessage `json:"tags"` +} + +func (v *CreateEnvironmentInput) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *CreateEnvironmentInput) __premarshalJSON() (*__premarshalCreateEnvironmentInput, error) { + var retval __premarshalCreateEnvironmentInput + + retval.Description = v.Description + retval.Id = v.Id + retval.Name = v.Name + { + + dst := &retval.Tags + src := v.Tags + var err error + *dst, err = scalars.MarshalJSON( + &src) + if err != nil { + return nil, fmt.Errorf( + "unable to marshal CreateEnvironmentInput.Tags: %w", err) + } + } + return &retval, nil +} + +// Create a new project. A project is the complete model of your application—its infrastructure, architecture, configurations, and environments. +type CreateProjectInput struct { + // An optional description of the project's purpose or contents + Description string `json:"description"` + // A short, memorable identifier for looking up this project in the API and CLI. This becomes the first segment of all resource identifiers within the project. For example, a project 'ecomm' with environment 'prod' and component 'db' creates the package identifier 'ecomm-prod-db'. Choose something concise and meaningful—human-readable, not a UUID. Max 20 characters, lowercase alphanumeric only (a-z, 0-9). Immutable after creation. + Id string `json:"id"` + // A human-readable name for the project + Name string `json:"name"` + // Key-value tags for this project. Keys and values must be strings. Must conform to the organization's tag constraints for the project scope. + Tags map[string]any `json:"-"` +} + +// GetDescription returns CreateProjectInput.Description, and is useful for accessing the field via an interface. +func (v *CreateProjectInput) GetDescription() string { return v.Description } + +// GetId returns CreateProjectInput.Id, and is useful for accessing the field via an interface. +func (v *CreateProjectInput) GetId() string { return v.Id } + +// GetName returns CreateProjectInput.Name, and is useful for accessing the field via an interface. +func (v *CreateProjectInput) GetName() string { return v.Name } + +// GetTags returns CreateProjectInput.Tags, and is useful for accessing the field via an interface. +func (v *CreateProjectInput) GetTags() map[string]any { return v.Tags } + +func (v *CreateProjectInput) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *CreateProjectInput + Tags json.RawMessage `json:"tags"` + graphql.NoUnmarshalJSON + } + firstPass.CreateProjectInput = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + { + dst := &v.Tags + src := firstPass.Tags + if len(src) != 0 && string(src) != "null" { + err = scalars.UnmarshalJSON( + src, dst) + if err != nil { + return fmt.Errorf( + "unable to unmarshal CreateProjectInput.Tags: %w", err) + } + } + } + return nil +} + +type __premarshalCreateProjectInput struct { + Description string `json:"description"` + + Id string `json:"id"` + + Name string `json:"name"` + + Tags json.RawMessage `json:"tags"` +} + +func (v *CreateProjectInput) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *CreateProjectInput) __premarshalJSON() (*__premarshalCreateProjectInput, error) { + var retval __premarshalCreateProjectInput + + retval.Description = v.Description + retval.Id = v.Id + retval.Name = v.Name + { + + dst := &retval.Tags + src := v.Tags + var err error + *dst, err = scalars.MarshalJSON( + &src) + if err != nil { + return nil, fmt.Errorf( + "unable to marshal CreateProjectInput.Tags: %w", err) + } + } + return &retval, nil +} + +type Cursor struct { + // Maximum number of items per page. Default: 20. Minimum: 1. Maximum: 100. + Limit int `json:"limit"` + // Cursor after which to return items + Next string `json:"next"` + // Cursor before which to return items + Previous string `json:"previous"` +} + +// GetLimit returns Cursor.Limit, and is useful for accessing the field via an interface. +func (v *Cursor) GetLimit() int { return v.Limit } + +// GetNext returns Cursor.Next, and is useful for accessing the field via an interface. +func (v *Cursor) GetNext() string { return v.Next } + +// GetPrevious returns Cursor.Previous, and is useful for accessing the field via an interface. +func (v *Cursor) GetPrevious() string { return v.Previous } + +// Filter which environments to return. +type EnvironmentsFilter struct { + // Filter by project ID + ProjectId IdFilter `json:"projectId"` + // Filter by environment ID + Id StringFilter `json:"id"` +} + +// GetProjectId returns EnvironmentsFilter.ProjectId, and is useful for accessing the field via an interface. +func (v *EnvironmentsFilter) GetProjectId() IdFilter { return v.ProjectId } + +// GetId returns EnvironmentsFilter.Id, and is useful for accessing the field via an interface. +func (v *EnvironmentsFilter) GetId() StringFilter { return v.Id } + +// Sorting options for the environments list. +type EnvironmentsSort struct { + // Which field to sort by + Field EnvironmentsSortField `json:"field"` + // Sort direction + Order SortOrder `json:"order"` +} + +// GetField returns EnvironmentsSort.Field, and is useful for accessing the field via an interface. +func (v *EnvironmentsSort) GetField() EnvironmentsSortField { return v.Field } + +// GetOrder returns EnvironmentsSort.Order, and is useful for accessing the field via an interface. +func (v *EnvironmentsSort) GetOrder() SortOrder { return v.Order } + +// Available fields for sorting environments. +type EnvironmentsSortField string + +const ( + // Sort alphabetically by environment name + EnvironmentsSortFieldName EnvironmentsSortField = "NAME" + // Sort by when the environment was created + EnvironmentsSortFieldCreatedAt EnvironmentsSortField = "CREATED_AT" +) + +var AllEnvironmentsSortField = []EnvironmentsSortField{ + EnvironmentsSortFieldName, + EnvironmentsSortFieldCreatedAt, +} + +// Filter for ID fields. All provided operators are combined with AND. +type IdFilter struct { + // Exact match + Eq string `json:"eq"` + // Match any of these values + In []string `json:"in"` +} + +// GetEq returns IdFilter.Eq, and is useful for accessing the field via an interface. +func (v *IdFilter) GetEq() string { return v.Eq } + +// GetIn returns IdFilter.In, and is useful for accessing the field via an interface. +func (v *IdFilter) GetIn() []string { return v.In } + +// The current state of an instance. +type InstanceStatus string + +const ( + // Created but not yet deployed + InstanceStatusInitialized InstanceStatus = "INITIALIZED" + // Successfully deployed and running + InstanceStatusProvisioned InstanceStatus = "PROVISIONED" + // Infrastructure has been torn down + InstanceStatusDecommissioned InstanceStatus = "DECOMMISSIONED" + // Deployment failed - check the deployment logs + InstanceStatusFailed InstanceStatus = "FAILED" + // Imported infrastructure managed outside Massdriver + InstanceStatusExternal InstanceStatus = "EXTERNAL" +) + +var AllInstanceStatus = []InstanceStatus{ + InstanceStatusInitialized, + InstanceStatusProvisioned, + InstanceStatusDecommissioned, + InstanceStatusFailed, + InstanceStatusExternal, +} + +// Which bundle releases an instance will use. +type ReleaseStrategy string + +const ( + // Only use stable, published releases + ReleaseStrategyStable ReleaseStrategy = "STABLE" + // Use development releases for testing + ReleaseStrategyDevelopment ReleaseStrategy = "DEVELOPMENT" +) + +var AllReleaseStrategy = []ReleaseStrategy{ + ReleaseStrategyStable, + ReleaseStrategyDevelopment, +} + +// Sort direction for paginated results. +type SortOrder string + +const ( + // Ascending order (A→Z, oldest→newest) + SortOrderAsc SortOrder = "ASC" + // Descending order (Z→A, newest→oldest) + SortOrderDesc SortOrder = "DESC" +) + +var AllSortOrder = []SortOrder{ + SortOrderAsc, + SortOrderDesc, +} + +// Filter for string fields. All provided operators are combined with AND. +type StringFilter struct { + // Exact match + Eq string `json:"eq"` + // Match any of these values + In []string `json:"in"` +} + +// GetEq returns StringFilter.Eq, and is useful for accessing the field via an interface. +func (v *StringFilter) GetEq() string { return v.Eq } + +// GetIn returns StringFilter.In, and is useful for accessing the field via an interface. +func (v *StringFilter) GetIn() []string { return v.In } + +// Update an existing environment's name and description. The ID cannot be changed after creation. +type UpdateEnvironmentInput struct { + // An optional description of the environment's purpose + Description string `json:"description"` + // A human-readable name for the environment + Name string `json:"name"` + // Key-value tags for this environment. Keys and values must be strings. Must conform to the organization's tag constraints for the environment scope. + Tags map[string]any `json:"-"` +} + +// GetDescription returns UpdateEnvironmentInput.Description, and is useful for accessing the field via an interface. +func (v *UpdateEnvironmentInput) GetDescription() string { return v.Description } + +// GetName returns UpdateEnvironmentInput.Name, and is useful for accessing the field via an interface. +func (v *UpdateEnvironmentInput) GetName() string { return v.Name } + +// GetTags returns UpdateEnvironmentInput.Tags, and is useful for accessing the field via an interface. +func (v *UpdateEnvironmentInput) GetTags() map[string]any { return v.Tags } + +func (v *UpdateEnvironmentInput) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *UpdateEnvironmentInput + Tags json.RawMessage `json:"tags"` + graphql.NoUnmarshalJSON + } + firstPass.UpdateEnvironmentInput = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + { + dst := &v.Tags + src := firstPass.Tags + if len(src) != 0 && string(src) != "null" { + err = scalars.UnmarshalJSON( + src, dst) + if err != nil { + return fmt.Errorf( + "unable to unmarshal UpdateEnvironmentInput.Tags: %w", err) + } + } + } + return nil +} + +type __premarshalUpdateEnvironmentInput struct { + Description string `json:"description"` + + Name string `json:"name"` + + Tags json.RawMessage `json:"tags"` +} + +func (v *UpdateEnvironmentInput) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *UpdateEnvironmentInput) __premarshalJSON() (*__premarshalUpdateEnvironmentInput, error) { + var retval __premarshalUpdateEnvironmentInput + + retval.Description = v.Description + retval.Name = v.Name + { + + dst := &retval.Tags + src := v.Tags + var err error + *dst, err = scalars.MarshalJSON( + &src) + if err != nil { + return nil, fmt.Errorf( + "unable to marshal UpdateEnvironmentInput.Tags: %w", err) + } + } + return &retval, nil +} + +// Update an existing project's name and description. The ID cannot be changed after creation. +type UpdateProjectInput struct { + // An optional description of the project's purpose or contents + Description string `json:"description"` + // A human-readable name for the project + Name string `json:"name"` + // Key-value tags for this project. Keys and values must be strings. Must conform to the organization's tag constraints for the project scope. + Tags map[string]any `json:"-"` +} + +// GetDescription returns UpdateProjectInput.Description, and is useful for accessing the field via an interface. +func (v *UpdateProjectInput) GetDescription() string { return v.Description } + +// GetName returns UpdateProjectInput.Name, and is useful for accessing the field via an interface. +func (v *UpdateProjectInput) GetName() string { return v.Name } + +// GetTags returns UpdateProjectInput.Tags, and is useful for accessing the field via an interface. +func (v *UpdateProjectInput) GetTags() map[string]any { return v.Tags } + +func (v *UpdateProjectInput) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *UpdateProjectInput + Tags json.RawMessage `json:"tags"` + graphql.NoUnmarshalJSON + } + firstPass.UpdateProjectInput = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + { + dst := &v.Tags + src := firstPass.Tags + if len(src) != 0 && string(src) != "null" { + err = scalars.UnmarshalJSON( + src, dst) + if err != nil { + return fmt.Errorf( + "unable to unmarshal UpdateProjectInput.Tags: %w", err) + } + } + } + return nil +} + +type __premarshalUpdateProjectInput struct { + Description string `json:"description"` + + Name string `json:"name"` + + Tags json.RawMessage `json:"tags"` +} + +func (v *UpdateProjectInput) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *UpdateProjectInput) __premarshalJSON() (*__premarshalUpdateProjectInput, error) { + var retval __premarshalUpdateProjectInput + + retval.Description = v.Description + retval.Name = v.Name + { + + dst := &retval.Tags + src := v.Tags + var err error + *dst, err = scalars.MarshalJSON( + &src) + if err != nil { + return nil, fmt.Errorf( + "unable to marshal UpdateProjectInput.Tags: %w", err) + } + } + return &retval, nil +} + +// __createEnvironmentInput is used internally by genqlient +type __createEnvironmentInput struct { + OrganizationId string `json:"organizationId"` + ProjectId string `json:"projectId"` + Input CreateEnvironmentInput `json:"input"` +} + +// GetOrganizationId returns __createEnvironmentInput.OrganizationId, and is useful for accessing the field via an interface. +func (v *__createEnvironmentInput) GetOrganizationId() string { return v.OrganizationId } + +// GetProjectId returns __createEnvironmentInput.ProjectId, and is useful for accessing the field via an interface. +func (v *__createEnvironmentInput) GetProjectId() string { return v.ProjectId } + +// GetInput returns __createEnvironmentInput.Input, and is useful for accessing the field via an interface. +func (v *__createEnvironmentInput) GetInput() CreateEnvironmentInput { return v.Input } + +// __createProjectInput is used internally by genqlient +type __createProjectInput struct { + OrganizationId string `json:"organizationId"` + Input CreateProjectInput `json:"input"` +} + +// GetOrganizationId returns __createProjectInput.OrganizationId, and is useful for accessing the field via an interface. +func (v *__createProjectInput) GetOrganizationId() string { return v.OrganizationId } + +// GetInput returns __createProjectInput.Input, and is useful for accessing the field via an interface. +func (v *__createProjectInput) GetInput() CreateProjectInput { return v.Input } + +// __deleteEnvironmentInput is used internally by genqlient +type __deleteEnvironmentInput struct { + OrganizationId string `json:"organizationId"` + Id string `json:"id"` +} + +// GetOrganizationId returns __deleteEnvironmentInput.OrganizationId, and is useful for accessing the field via an interface. +func (v *__deleteEnvironmentInput) GetOrganizationId() string { return v.OrganizationId } + +// GetId returns __deleteEnvironmentInput.Id, and is useful for accessing the field via an interface. +func (v *__deleteEnvironmentInput) GetId() string { return v.Id } + +// __deleteProjectInput is used internally by genqlient +type __deleteProjectInput struct { + OrganizationId string `json:"organizationId"` + Id string `json:"id"` +} + +// GetOrganizationId returns __deleteProjectInput.OrganizationId, and is useful for accessing the field via an interface. +func (v *__deleteProjectInput) GetOrganizationId() string { return v.OrganizationId } + +// GetId returns __deleteProjectInput.Id, and is useful for accessing the field via an interface. +func (v *__deleteProjectInput) GetId() string { return v.Id } + +// __getEnvironmentInput is used internally by genqlient +type __getEnvironmentInput struct { + OrganizationId string `json:"organizationId"` + Id string `json:"id"` +} + +// GetOrganizationId returns __getEnvironmentInput.OrganizationId, and is useful for accessing the field via an interface. +func (v *__getEnvironmentInput) GetOrganizationId() string { return v.OrganizationId } + +// GetId returns __getEnvironmentInput.Id, and is useful for accessing the field via an interface. +func (v *__getEnvironmentInput) GetId() string { return v.Id } + +// __getProjectInput is used internally by genqlient +type __getProjectInput struct { + OrganizationId string `json:"organizationId"` + Id string `json:"id"` +} + +// GetOrganizationId returns __getProjectInput.OrganizationId, and is useful for accessing the field via an interface. +func (v *__getProjectInput) GetOrganizationId() string { return v.OrganizationId } + +// GetId returns __getProjectInput.Id, and is useful for accessing the field via an interface. +func (v *__getProjectInput) GetId() string { return v.Id } + +// __listEnvironmentsInput is used internally by genqlient +type __listEnvironmentsInput struct { + OrganizationId string `json:"organizationId"` + Filter *EnvironmentsFilter `json:"filter,omitempty"` + Sort *EnvironmentsSort `json:"sort,omitempty"` + Cursor *Cursor `json:"cursor,omitempty"` +} + +// GetOrganizationId returns __listEnvironmentsInput.OrganizationId, and is useful for accessing the field via an interface. +func (v *__listEnvironmentsInput) GetOrganizationId() string { return v.OrganizationId } + +// GetFilter returns __listEnvironmentsInput.Filter, and is useful for accessing the field via an interface. +func (v *__listEnvironmentsInput) GetFilter() *EnvironmentsFilter { return v.Filter } + +// GetSort returns __listEnvironmentsInput.Sort, and is useful for accessing the field via an interface. +func (v *__listEnvironmentsInput) GetSort() *EnvironmentsSort { return v.Sort } + +// GetCursor returns __listEnvironmentsInput.Cursor, and is useful for accessing the field via an interface. +func (v *__listEnvironmentsInput) GetCursor() *Cursor { return v.Cursor } + +// __listProjectsInput is used internally by genqlient +type __listProjectsInput struct { + OrganizationId string `json:"organizationId"` +} + +// GetOrganizationId returns __listProjectsInput.OrganizationId, and is useful for accessing the field via an interface. +func (v *__listProjectsInput) GetOrganizationId() string { return v.OrganizationId } + +// __updateEnvironmentInput is used internally by genqlient +type __updateEnvironmentInput struct { + OrganizationId string `json:"organizationId"` + Id string `json:"id"` + Input UpdateEnvironmentInput `json:"input"` +} + +// GetOrganizationId returns __updateEnvironmentInput.OrganizationId, and is useful for accessing the field via an interface. +func (v *__updateEnvironmentInput) GetOrganizationId() string { return v.OrganizationId } + +// GetId returns __updateEnvironmentInput.Id, and is useful for accessing the field via an interface. +func (v *__updateEnvironmentInput) GetId() string { return v.Id } + +// GetInput returns __updateEnvironmentInput.Input, and is useful for accessing the field via an interface. +func (v *__updateEnvironmentInput) GetInput() UpdateEnvironmentInput { return v.Input } + +// __updateProjectInput is used internally by genqlient +type __updateProjectInput struct { + OrganizationId string `json:"organizationId"` + Id string `json:"id"` + Input UpdateProjectInput `json:"input"` +} + +// GetOrganizationId returns __updateProjectInput.OrganizationId, and is useful for accessing the field via an interface. +func (v *__updateProjectInput) GetOrganizationId() string { return v.OrganizationId } + +// GetId returns __updateProjectInput.Id, and is useful for accessing the field via an interface. +func (v *__updateProjectInput) GetId() string { return v.Id } + +// GetInput returns __updateProjectInput.Input, and is useful for accessing the field via an interface. +func (v *__updateProjectInput) GetInput() UpdateProjectInput { return v.Input } + +// createEnvironmentCreateEnvironmentEnvironmentPayload includes the requested fields of the GraphQL type EnvironmentPayload. +type createEnvironmentCreateEnvironmentEnvironmentPayload struct { + // The object created/updated/deleted by the mutation. May be null if mutation failed. + Result createEnvironmentCreateEnvironmentEnvironmentPayloadResultEnvironment `json:"result"` + // Indicates if the mutation completed successfully or not. + Successful bool `json:"successful"` + // A list of failed validations. May be blank or null if mutation succeeded. + Messages []createEnvironmentCreateEnvironmentEnvironmentPayloadMessagesValidationMessage `json:"messages"` +} + +// GetResult returns createEnvironmentCreateEnvironmentEnvironmentPayload.Result, and is useful for accessing the field via an interface. +func (v *createEnvironmentCreateEnvironmentEnvironmentPayload) GetResult() createEnvironmentCreateEnvironmentEnvironmentPayloadResultEnvironment { + return v.Result +} + +// GetSuccessful returns createEnvironmentCreateEnvironmentEnvironmentPayload.Successful, and is useful for accessing the field via an interface. +func (v *createEnvironmentCreateEnvironmentEnvironmentPayload) GetSuccessful() bool { + return v.Successful +} + +// GetMessages returns createEnvironmentCreateEnvironmentEnvironmentPayload.Messages, and is useful for accessing the field via an interface. +func (v *createEnvironmentCreateEnvironmentEnvironmentPayload) GetMessages() []createEnvironmentCreateEnvironmentEnvironmentPayloadMessagesValidationMessage { + return v.Messages +} + +// createEnvironmentCreateEnvironmentEnvironmentPayloadMessagesValidationMessage includes the requested fields of the GraphQL type ValidationMessage. +// The GraphQL type's documentation follows. +// +// Validation messages are returned when mutation input does not meet the requirements. +// While client-side validation is highly recommended to provide the best User Experience, +// All inputs will always be validated server-side. +// +// Some examples of validations are: +// +// * Username must be at least 10 characters +// * Email field does not contain an email address +// * Birth Date is required +// +// While GraphQL has support for required values, mutation data fields are always +// set to optional in our API. This allows 'required field' messages +// to be returned in the same manner as other validations. The only exceptions +// are id fields, which may be required to perform updates or deletes. +type createEnvironmentCreateEnvironmentEnvironmentPayloadMessagesValidationMessage struct { + // A unique error code for the type of validation used. + Code string `json:"code"` + // The input field that the error applies to. The field can be used to + // identify which field the error message should be displayed next to in the + // presentation layer. + // + // If there are multiple errors to display for a field, multiple validation + // messages will be in the result. + // + // This field may be null in cases where an error cannot be applied to a specific field. + Field string `json:"field"` + // A friendly error message, appropriate for display to the end user. + // + // The message is interpolated to include the appropriate variables. + // + // Example: `Username must be at least 10 characters` + // + // This message may change without notice, so we do not recommend you match against the text. + // Instead, use the *code* field for matching. + Message string `json:"message"` +} + +// GetCode returns createEnvironmentCreateEnvironmentEnvironmentPayloadMessagesValidationMessage.Code, and is useful for accessing the field via an interface. +func (v *createEnvironmentCreateEnvironmentEnvironmentPayloadMessagesValidationMessage) GetCode() string { + return v.Code +} + +// GetField returns createEnvironmentCreateEnvironmentEnvironmentPayloadMessagesValidationMessage.Field, and is useful for accessing the field via an interface. +func (v *createEnvironmentCreateEnvironmentEnvironmentPayloadMessagesValidationMessage) GetField() string { + return v.Field +} + +// GetMessage returns createEnvironmentCreateEnvironmentEnvironmentPayloadMessagesValidationMessage.Message, and is useful for accessing the field via an interface. +func (v *createEnvironmentCreateEnvironmentEnvironmentPayloadMessagesValidationMessage) GetMessage() string { + return v.Message +} + +// createEnvironmentCreateEnvironmentEnvironmentPayloadResultEnvironment includes the requested fields of the GraphQL type Environment. +// The GraphQL type's documentation follows. +// +// An environment. +type createEnvironmentCreateEnvironmentEnvironmentPayloadResultEnvironment struct { + Id string `json:"id"` + // Display name + Name string `json:"name"` + // What this environment is for + Description string `json:"description"` +} + +// GetId returns createEnvironmentCreateEnvironmentEnvironmentPayloadResultEnvironment.Id, and is useful for accessing the field via an interface. +func (v *createEnvironmentCreateEnvironmentEnvironmentPayloadResultEnvironment) GetId() string { + return v.Id +} + +// GetName returns createEnvironmentCreateEnvironmentEnvironmentPayloadResultEnvironment.Name, and is useful for accessing the field via an interface. +func (v *createEnvironmentCreateEnvironmentEnvironmentPayloadResultEnvironment) GetName() string { + return v.Name +} + +// GetDescription returns createEnvironmentCreateEnvironmentEnvironmentPayloadResultEnvironment.Description, and is useful for accessing the field via an interface. +func (v *createEnvironmentCreateEnvironmentEnvironmentPayloadResultEnvironment) GetDescription() string { + return v.Description +} + +// createEnvironmentResponse is returned by createEnvironment on success. +type createEnvironmentResponse struct { + // Create a new environment in a project. + CreateEnvironment createEnvironmentCreateEnvironmentEnvironmentPayload `json:"createEnvironment"` +} + +// GetCreateEnvironment returns createEnvironmentResponse.CreateEnvironment, and is useful for accessing the field via an interface. +func (v *createEnvironmentResponse) GetCreateEnvironment() createEnvironmentCreateEnvironmentEnvironmentPayload { + return v.CreateEnvironment +} + +// createProjectCreateProjectProjectPayload includes the requested fields of the GraphQL type ProjectPayload. +type createProjectCreateProjectProjectPayload struct { + // The object created/updated/deleted by the mutation. May be null if mutation failed. + Result createProjectCreateProjectProjectPayloadResultProject `json:"result"` + // Indicates if the mutation completed successfully or not. + Successful bool `json:"successful"` + // A list of failed validations. May be blank or null if mutation succeeded. + Messages []createProjectCreateProjectProjectPayloadMessagesValidationMessage `json:"messages"` +} + +// GetResult returns createProjectCreateProjectProjectPayload.Result, and is useful for accessing the field via an interface. +func (v *createProjectCreateProjectProjectPayload) GetResult() createProjectCreateProjectProjectPayloadResultProject { + return v.Result +} + +// GetSuccessful returns createProjectCreateProjectProjectPayload.Successful, and is useful for accessing the field via an interface. +func (v *createProjectCreateProjectProjectPayload) GetSuccessful() bool { return v.Successful } + +// GetMessages returns createProjectCreateProjectProjectPayload.Messages, and is useful for accessing the field via an interface. +func (v *createProjectCreateProjectProjectPayload) GetMessages() []createProjectCreateProjectProjectPayloadMessagesValidationMessage { + return v.Messages +} + +// createProjectCreateProjectProjectPayloadMessagesValidationMessage includes the requested fields of the GraphQL type ValidationMessage. +// The GraphQL type's documentation follows. +// +// Validation messages are returned when mutation input does not meet the requirements. +// While client-side validation is highly recommended to provide the best User Experience, +// All inputs will always be validated server-side. +// +// Some examples of validations are: +// +// * Username must be at least 10 characters +// * Email field does not contain an email address +// * Birth Date is required +// +// While GraphQL has support for required values, mutation data fields are always +// set to optional in our API. This allows 'required field' messages +// to be returned in the same manner as other validations. The only exceptions +// are id fields, which may be required to perform updates or deletes. +type createProjectCreateProjectProjectPayloadMessagesValidationMessage struct { + // A unique error code for the type of validation used. + Code string `json:"code"` + // The input field that the error applies to. The field can be used to + // identify which field the error message should be displayed next to in the + // presentation layer. + // + // If there are multiple errors to display for a field, multiple validation + // messages will be in the result. + // + // This field may be null in cases where an error cannot be applied to a specific field. + Field string `json:"field"` + // A friendly error message, appropriate for display to the end user. + // + // The message is interpolated to include the appropriate variables. + // + // Example: `Username must be at least 10 characters` + // + // This message may change without notice, so we do not recommend you match against the text. + // Instead, use the *code* field for matching. + Message string `json:"message"` +} + +// GetCode returns createProjectCreateProjectProjectPayloadMessagesValidationMessage.Code, and is useful for accessing the field via an interface. +func (v *createProjectCreateProjectProjectPayloadMessagesValidationMessage) GetCode() string { + return v.Code +} + +// GetField returns createProjectCreateProjectProjectPayloadMessagesValidationMessage.Field, and is useful for accessing the field via an interface. +func (v *createProjectCreateProjectProjectPayloadMessagesValidationMessage) GetField() string { + return v.Field +} + +// GetMessage returns createProjectCreateProjectProjectPayloadMessagesValidationMessage.Message, and is useful for accessing the field via an interface. +func (v *createProjectCreateProjectProjectPayloadMessagesValidationMessage) GetMessage() string { + return v.Message +} + +// createProjectCreateProjectProjectPayloadResultProject includes the requested fields of the GraphQL type Project. +// The GraphQL type's documentation follows. +// +// A project. +type createProjectCreateProjectProjectPayloadResultProject struct { + Id string `json:"id"` + // Display name + Name string `json:"name"` + // What this project is for + Description string `json:"description"` +} + +// GetId returns createProjectCreateProjectProjectPayloadResultProject.Id, and is useful for accessing the field via an interface. +func (v *createProjectCreateProjectProjectPayloadResultProject) GetId() string { return v.Id } + +// GetName returns createProjectCreateProjectProjectPayloadResultProject.Name, and is useful for accessing the field via an interface. +func (v *createProjectCreateProjectProjectPayloadResultProject) GetName() string { return v.Name } + +// GetDescription returns createProjectCreateProjectProjectPayloadResultProject.Description, and is useful for accessing the field via an interface. +func (v *createProjectCreateProjectProjectPayloadResultProject) GetDescription() string { + return v.Description +} + +// createProjectResponse is returned by createProject on success. +type createProjectResponse struct { + // Create a new project in your organization. + CreateProject createProjectCreateProjectProjectPayload `json:"createProject"` +} + +// GetCreateProject returns createProjectResponse.CreateProject, and is useful for accessing the field via an interface. +func (v *createProjectResponse) GetCreateProject() createProjectCreateProjectProjectPayload { + return v.CreateProject +} + +// deleteEnvironmentDeleteEnvironmentEnvironmentPayload includes the requested fields of the GraphQL type EnvironmentPayload. +type deleteEnvironmentDeleteEnvironmentEnvironmentPayload struct { + // The object created/updated/deleted by the mutation. May be null if mutation failed. + Result deleteEnvironmentDeleteEnvironmentEnvironmentPayloadResultEnvironment `json:"result"` + // Indicates if the mutation completed successfully or not. + Successful bool `json:"successful"` + // A list of failed validations. May be blank or null if mutation succeeded. + Messages []deleteEnvironmentDeleteEnvironmentEnvironmentPayloadMessagesValidationMessage `json:"messages"` +} + +// GetResult returns deleteEnvironmentDeleteEnvironmentEnvironmentPayload.Result, and is useful for accessing the field via an interface. +func (v *deleteEnvironmentDeleteEnvironmentEnvironmentPayload) GetResult() deleteEnvironmentDeleteEnvironmentEnvironmentPayloadResultEnvironment { + return v.Result +} + +// GetSuccessful returns deleteEnvironmentDeleteEnvironmentEnvironmentPayload.Successful, and is useful for accessing the field via an interface. +func (v *deleteEnvironmentDeleteEnvironmentEnvironmentPayload) GetSuccessful() bool { + return v.Successful +} + +// GetMessages returns deleteEnvironmentDeleteEnvironmentEnvironmentPayload.Messages, and is useful for accessing the field via an interface. +func (v *deleteEnvironmentDeleteEnvironmentEnvironmentPayload) GetMessages() []deleteEnvironmentDeleteEnvironmentEnvironmentPayloadMessagesValidationMessage { + return v.Messages +} + +// deleteEnvironmentDeleteEnvironmentEnvironmentPayloadMessagesValidationMessage includes the requested fields of the GraphQL type ValidationMessage. +// The GraphQL type's documentation follows. +// +// Validation messages are returned when mutation input does not meet the requirements. +// While client-side validation is highly recommended to provide the best User Experience, +// All inputs will always be validated server-side. +// +// Some examples of validations are: +// +// * Username must be at least 10 characters +// * Email field does not contain an email address +// * Birth Date is required +// +// While GraphQL has support for required values, mutation data fields are always +// set to optional in our API. This allows 'required field' messages +// to be returned in the same manner as other validations. The only exceptions +// are id fields, which may be required to perform updates or deletes. +type deleteEnvironmentDeleteEnvironmentEnvironmentPayloadMessagesValidationMessage struct { + // A unique error code for the type of validation used. + Code string `json:"code"` + // The input field that the error applies to. The field can be used to + // identify which field the error message should be displayed next to in the + // presentation layer. + // + // If there are multiple errors to display for a field, multiple validation + // messages will be in the result. + // + // This field may be null in cases where an error cannot be applied to a specific field. + Field string `json:"field"` + // A friendly error message, appropriate for display to the end user. + // + // The message is interpolated to include the appropriate variables. + // + // Example: `Username must be at least 10 characters` + // + // This message may change without notice, so we do not recommend you match against the text. + // Instead, use the *code* field for matching. + Message string `json:"message"` +} + +// GetCode returns deleteEnvironmentDeleteEnvironmentEnvironmentPayloadMessagesValidationMessage.Code, and is useful for accessing the field via an interface. +func (v *deleteEnvironmentDeleteEnvironmentEnvironmentPayloadMessagesValidationMessage) GetCode() string { + return v.Code +} + +// GetField returns deleteEnvironmentDeleteEnvironmentEnvironmentPayloadMessagesValidationMessage.Field, and is useful for accessing the field via an interface. +func (v *deleteEnvironmentDeleteEnvironmentEnvironmentPayloadMessagesValidationMessage) GetField() string { + return v.Field +} + +// GetMessage returns deleteEnvironmentDeleteEnvironmentEnvironmentPayloadMessagesValidationMessage.Message, and is useful for accessing the field via an interface. +func (v *deleteEnvironmentDeleteEnvironmentEnvironmentPayloadMessagesValidationMessage) GetMessage() string { + return v.Message +} + +// deleteEnvironmentDeleteEnvironmentEnvironmentPayloadResultEnvironment includes the requested fields of the GraphQL type Environment. +// The GraphQL type's documentation follows. +// +// An environment. +type deleteEnvironmentDeleteEnvironmentEnvironmentPayloadResultEnvironment struct { + Id string `json:"id"` + // Display name + Name string `json:"name"` + // What this environment is for + Description string `json:"description"` +} + +// GetId returns deleteEnvironmentDeleteEnvironmentEnvironmentPayloadResultEnvironment.Id, and is useful for accessing the field via an interface. +func (v *deleteEnvironmentDeleteEnvironmentEnvironmentPayloadResultEnvironment) GetId() string { + return v.Id +} + +// GetName returns deleteEnvironmentDeleteEnvironmentEnvironmentPayloadResultEnvironment.Name, and is useful for accessing the field via an interface. +func (v *deleteEnvironmentDeleteEnvironmentEnvironmentPayloadResultEnvironment) GetName() string { + return v.Name +} + +// GetDescription returns deleteEnvironmentDeleteEnvironmentEnvironmentPayloadResultEnvironment.Description, and is useful for accessing the field via an interface. +func (v *deleteEnvironmentDeleteEnvironmentEnvironmentPayloadResultEnvironment) GetDescription() string { + return v.Description +} + +// deleteEnvironmentResponse is returned by deleteEnvironment on success. +type deleteEnvironmentResponse struct { + // Delete an environment. You must decommission all packages first. + DeleteEnvironment deleteEnvironmentDeleteEnvironmentEnvironmentPayload `json:"deleteEnvironment"` +} + +// GetDeleteEnvironment returns deleteEnvironmentResponse.DeleteEnvironment, and is useful for accessing the field via an interface. +func (v *deleteEnvironmentResponse) GetDeleteEnvironment() deleteEnvironmentDeleteEnvironmentEnvironmentPayload { + return v.DeleteEnvironment +} + +// deleteProjectDeleteProjectProjectPayload includes the requested fields of the GraphQL type ProjectPayload. +type deleteProjectDeleteProjectProjectPayload struct { + // The object created/updated/deleted by the mutation. May be null if mutation failed. + Result deleteProjectDeleteProjectProjectPayloadResultProject `json:"result"` + // Indicates if the mutation completed successfully or not. + Successful bool `json:"successful"` + // A list of failed validations. May be blank or null if mutation succeeded. + Messages []deleteProjectDeleteProjectProjectPayloadMessagesValidationMessage `json:"messages"` +} + +// GetResult returns deleteProjectDeleteProjectProjectPayload.Result, and is useful for accessing the field via an interface. +func (v *deleteProjectDeleteProjectProjectPayload) GetResult() deleteProjectDeleteProjectProjectPayloadResultProject { + return v.Result +} + +// GetSuccessful returns deleteProjectDeleteProjectProjectPayload.Successful, and is useful for accessing the field via an interface. +func (v *deleteProjectDeleteProjectProjectPayload) GetSuccessful() bool { return v.Successful } + +// GetMessages returns deleteProjectDeleteProjectProjectPayload.Messages, and is useful for accessing the field via an interface. +func (v *deleteProjectDeleteProjectProjectPayload) GetMessages() []deleteProjectDeleteProjectProjectPayloadMessagesValidationMessage { + return v.Messages +} + +// deleteProjectDeleteProjectProjectPayloadMessagesValidationMessage includes the requested fields of the GraphQL type ValidationMessage. +// The GraphQL type's documentation follows. +// +// Validation messages are returned when mutation input does not meet the requirements. +// While client-side validation is highly recommended to provide the best User Experience, +// All inputs will always be validated server-side. +// +// Some examples of validations are: +// +// * Username must be at least 10 characters +// * Email field does not contain an email address +// * Birth Date is required +// +// While GraphQL has support for required values, mutation data fields are always +// set to optional in our API. This allows 'required field' messages +// to be returned in the same manner as other validations. The only exceptions +// are id fields, which may be required to perform updates or deletes. +type deleteProjectDeleteProjectProjectPayloadMessagesValidationMessage struct { + // A unique error code for the type of validation used. + Code string `json:"code"` + // The input field that the error applies to. The field can be used to + // identify which field the error message should be displayed next to in the + // presentation layer. + // + // If there are multiple errors to display for a field, multiple validation + // messages will be in the result. + // + // This field may be null in cases where an error cannot be applied to a specific field. + Field string `json:"field"` + // A friendly error message, appropriate for display to the end user. + // + // The message is interpolated to include the appropriate variables. + // + // Example: `Username must be at least 10 characters` + // + // This message may change without notice, so we do not recommend you match against the text. + // Instead, use the *code* field for matching. + Message string `json:"message"` +} + +// GetCode returns deleteProjectDeleteProjectProjectPayloadMessagesValidationMessage.Code, and is useful for accessing the field via an interface. +func (v *deleteProjectDeleteProjectProjectPayloadMessagesValidationMessage) GetCode() string { + return v.Code +} + +// GetField returns deleteProjectDeleteProjectProjectPayloadMessagesValidationMessage.Field, and is useful for accessing the field via an interface. +func (v *deleteProjectDeleteProjectProjectPayloadMessagesValidationMessage) GetField() string { + return v.Field +} + +// GetMessage returns deleteProjectDeleteProjectProjectPayloadMessagesValidationMessage.Message, and is useful for accessing the field via an interface. +func (v *deleteProjectDeleteProjectProjectPayloadMessagesValidationMessage) GetMessage() string { + return v.Message +} + +// deleteProjectDeleteProjectProjectPayloadResultProject includes the requested fields of the GraphQL type Project. +// The GraphQL type's documentation follows. +// +// A project. +type deleteProjectDeleteProjectProjectPayloadResultProject struct { + Id string `json:"id"` + // Display name + Name string `json:"name"` + // What this project is for + Description string `json:"description"` +} + +// GetId returns deleteProjectDeleteProjectProjectPayloadResultProject.Id, and is useful for accessing the field via an interface. +func (v *deleteProjectDeleteProjectProjectPayloadResultProject) GetId() string { return v.Id } + +// GetName returns deleteProjectDeleteProjectProjectPayloadResultProject.Name, and is useful for accessing the field via an interface. +func (v *deleteProjectDeleteProjectProjectPayloadResultProject) GetName() string { return v.Name } + +// GetDescription returns deleteProjectDeleteProjectProjectPayloadResultProject.Description, and is useful for accessing the field via an interface. +func (v *deleteProjectDeleteProjectProjectPayloadResultProject) GetDescription() string { + return v.Description +} + +// deleteProjectResponse is returned by deleteProject on success. +type deleteProjectResponse struct { + // Delete a project. You must delete all environments first. + DeleteProject deleteProjectDeleteProjectProjectPayload `json:"deleteProject"` +} + +// GetDeleteProject returns deleteProjectResponse.DeleteProject, and is useful for accessing the field via an interface. +func (v *deleteProjectResponse) GetDeleteProject() deleteProjectDeleteProjectProjectPayload { + return v.DeleteProject +} + +// getEnvironmentEnvironment includes the requested fields of the GraphQL type Environment. +// The GraphQL type's documentation follows. +// +// An environment. +type getEnvironmentEnvironment struct { + Id string `json:"id"` + // Display name + Name string `json:"name"` + // What this environment is for + Description string `json:"description"` + // When this environment was created (UTC) + CreatedAt time.Time `json:"createdAt"` + // When this environment was last modified (UTC) + UpdatedAt time.Time `json:"updatedAt"` + // Cloud provider costs for this environment. + Cost getEnvironmentEnvironmentCostCostSummary `json:"cost"` + // The project containing this environment. + Project getEnvironmentEnvironmentProject `json:"project"` + // The realized infrastructure blueprint for this environment (instances and connections). + Blueprint getEnvironmentEnvironmentBlueprint `json:"blueprint"` +} + +// GetId returns getEnvironmentEnvironment.Id, and is useful for accessing the field via an interface. +func (v *getEnvironmentEnvironment) GetId() string { return v.Id } + +// GetName returns getEnvironmentEnvironment.Name, and is useful for accessing the field via an interface. +func (v *getEnvironmentEnvironment) GetName() string { return v.Name } + +// GetDescription returns getEnvironmentEnvironment.Description, and is useful for accessing the field via an interface. +func (v *getEnvironmentEnvironment) GetDescription() string { return v.Description } + +// GetCreatedAt returns getEnvironmentEnvironment.CreatedAt, and is useful for accessing the field via an interface. +func (v *getEnvironmentEnvironment) GetCreatedAt() time.Time { return v.CreatedAt } + +// GetUpdatedAt returns getEnvironmentEnvironment.UpdatedAt, and is useful for accessing the field via an interface. +func (v *getEnvironmentEnvironment) GetUpdatedAt() time.Time { return v.UpdatedAt } + +// GetCost returns getEnvironmentEnvironment.Cost, and is useful for accessing the field via an interface. +func (v *getEnvironmentEnvironment) GetCost() getEnvironmentEnvironmentCostCostSummary { return v.Cost } + +// GetProject returns getEnvironmentEnvironment.Project, and is useful for accessing the field via an interface. +func (v *getEnvironmentEnvironment) GetProject() getEnvironmentEnvironmentProject { return v.Project } + +// GetBlueprint returns getEnvironmentEnvironment.Blueprint, and is useful for accessing the field via an interface. +func (v *getEnvironmentEnvironment) GetBlueprint() getEnvironmentEnvironmentBlueprint { + return v.Blueprint +} + +// getEnvironmentEnvironmentBlueprint includes the requested fields of the GraphQL type EnvironmentBlueprint. +// The GraphQL type's documentation follows. +// +// An environment's realized infrastructure blueprint. +// +// The environment blueprint contains all instances (deployed packages) and +// connections (runtime wiring between them) showing how your infrastructure +// is actually deployed. This mirrors the project-level blueprint at the +// environment level. +type getEnvironmentEnvironmentBlueprint struct { + // Instances deployed in this environment. + Instances getEnvironmentEnvironmentBlueprintInstancesInstancesPage `json:"instances"` +} + +// GetInstances returns getEnvironmentEnvironmentBlueprint.Instances, and is useful for accessing the field via an interface. +func (v *getEnvironmentEnvironmentBlueprint) GetInstances() getEnvironmentEnvironmentBlueprintInstancesInstancesPage { + return v.Instances +} + +// getEnvironmentEnvironmentBlueprintInstancesInstancesPage includes the requested fields of the GraphQL type InstancesPage. +type getEnvironmentEnvironmentBlueprintInstancesInstancesPage struct { + // Pagination cursors + Cursor getEnvironmentEnvironmentBlueprintInstancesInstancesPageCursorPaginationCursor `json:"cursor"` + // A list of type instance. + Items []getEnvironmentEnvironmentBlueprintInstancesInstancesPageItemsInstance `json:"items"` +} + +// GetCursor returns getEnvironmentEnvironmentBlueprintInstancesInstancesPage.Cursor, and is useful for accessing the field via an interface. +func (v *getEnvironmentEnvironmentBlueprintInstancesInstancesPage) GetCursor() getEnvironmentEnvironmentBlueprintInstancesInstancesPageCursorPaginationCursor { + return v.Cursor +} + +// GetItems returns getEnvironmentEnvironmentBlueprintInstancesInstancesPage.Items, and is useful for accessing the field via an interface. +func (v *getEnvironmentEnvironmentBlueprintInstancesInstancesPage) GetItems() []getEnvironmentEnvironmentBlueprintInstancesInstancesPageItemsInstance { + return v.Items +} + +// getEnvironmentEnvironmentBlueprintInstancesInstancesPageCursorPaginationCursor includes the requested fields of the GraphQL type PaginationCursor. +type getEnvironmentEnvironmentBlueprintInstancesInstancesPageCursorPaginationCursor struct { + // Cursor to the next page + Next string `json:"next"` + // Cursor to the previous page + Previous string `json:"previous"` +} + +// GetNext returns getEnvironmentEnvironmentBlueprintInstancesInstancesPageCursorPaginationCursor.Next, and is useful for accessing the field via an interface. +func (v *getEnvironmentEnvironmentBlueprintInstancesInstancesPageCursorPaginationCursor) GetNext() string { + return v.Next +} + +// GetPrevious returns getEnvironmentEnvironmentBlueprintInstancesInstancesPageCursorPaginationCursor.Previous, and is useful for accessing the field via an interface. +func (v *getEnvironmentEnvironmentBlueprintInstancesInstancesPageCursorPaginationCursor) GetPrevious() string { + return v.Previous +} + +// getEnvironmentEnvironmentBlueprintInstancesInstancesPageItemsInstance includes the requested fields of the GraphQL type Instance. +// The GraphQL type's documentation follows. +// +// An instance. +type getEnvironmentEnvironmentBlueprintInstancesInstancesPageItemsInstance struct { + Id string `json:"id"` + // Display name + Name string `json:"name"` + // Current state of the instance + Status InstanceStatus `json:"status"` + // Version constraint (e.g., '~1.0' or '1.2.3') + Version string `json:"version"` + // Whether to use stable or development releases + ReleaseStrategy ReleaseStrategy `json:"releaseStrategy"` + // When this instance was created (UTC) + CreatedAt time.Time `json:"createdAt"` + // When this instance was last modified (UTC) + UpdatedAt time.Time `json:"updatedAt"` + // The bundle version currently deployed. + Bundle getEnvironmentEnvironmentBlueprintInstancesInstancesPageItemsInstanceBundle `json:"bundle"` +} + +// GetId returns getEnvironmentEnvironmentBlueprintInstancesInstancesPageItemsInstance.Id, and is useful for accessing the field via an interface. +func (v *getEnvironmentEnvironmentBlueprintInstancesInstancesPageItemsInstance) GetId() string { + return v.Id +} + +// GetName returns getEnvironmentEnvironmentBlueprintInstancesInstancesPageItemsInstance.Name, and is useful for accessing the field via an interface. +func (v *getEnvironmentEnvironmentBlueprintInstancesInstancesPageItemsInstance) GetName() string { + return v.Name +} + +// GetStatus returns getEnvironmentEnvironmentBlueprintInstancesInstancesPageItemsInstance.Status, and is useful for accessing the field via an interface. +func (v *getEnvironmentEnvironmentBlueprintInstancesInstancesPageItemsInstance) GetStatus() InstanceStatus { + return v.Status +} + +// GetVersion returns getEnvironmentEnvironmentBlueprintInstancesInstancesPageItemsInstance.Version, and is useful for accessing the field via an interface. +func (v *getEnvironmentEnvironmentBlueprintInstancesInstancesPageItemsInstance) GetVersion() string { + return v.Version +} + +// GetReleaseStrategy returns getEnvironmentEnvironmentBlueprintInstancesInstancesPageItemsInstance.ReleaseStrategy, and is useful for accessing the field via an interface. +func (v *getEnvironmentEnvironmentBlueprintInstancesInstancesPageItemsInstance) GetReleaseStrategy() ReleaseStrategy { + return v.ReleaseStrategy +} + +// GetCreatedAt returns getEnvironmentEnvironmentBlueprintInstancesInstancesPageItemsInstance.CreatedAt, and is useful for accessing the field via an interface. +func (v *getEnvironmentEnvironmentBlueprintInstancesInstancesPageItemsInstance) GetCreatedAt() time.Time { + return v.CreatedAt +} + +// GetUpdatedAt returns getEnvironmentEnvironmentBlueprintInstancesInstancesPageItemsInstance.UpdatedAt, and is useful for accessing the field via an interface. +func (v *getEnvironmentEnvironmentBlueprintInstancesInstancesPageItemsInstance) GetUpdatedAt() time.Time { + return v.UpdatedAt +} + +// GetBundle returns getEnvironmentEnvironmentBlueprintInstancesInstancesPageItemsInstance.Bundle, and is useful for accessing the field via an interface. +func (v *getEnvironmentEnvironmentBlueprintInstancesInstancesPageItemsInstance) GetBundle() getEnvironmentEnvironmentBlueprintInstancesInstancesPageItemsInstanceBundle { + return v.Bundle +} + +// getEnvironmentEnvironmentBlueprintInstancesInstancesPageItemsInstanceBundle includes the requested fields of the GraphQL type Bundle. +// The GraphQL type's documentation follows. +// +// A bundle. +type getEnvironmentEnvironmentBlueprintInstancesInstancesPageItemsInstanceBundle struct { + // Bundle name (e.g., 'aws-aurora-postgres') + Name string `json:"name"` + // Bundle identifier in `name@version` format (e.g., 'aws-aurora-postgres@1.2.3') + Id string `json:"id"` +} + +// GetName returns getEnvironmentEnvironmentBlueprintInstancesInstancesPageItemsInstanceBundle.Name, and is useful for accessing the field via an interface. +func (v *getEnvironmentEnvironmentBlueprintInstancesInstancesPageItemsInstanceBundle) GetName() string { + return v.Name +} + +// GetId returns getEnvironmentEnvironmentBlueprintInstancesInstancesPageItemsInstanceBundle.Id, and is useful for accessing the field via an interface. +func (v *getEnvironmentEnvironmentBlueprintInstancesInstancesPageItemsInstanceBundle) GetId() string { + return v.Id +} + +// getEnvironmentEnvironmentCostCostSummary includes the requested fields of the GraphQL type CostSummary. +// The GraphQL type's documentation follows. +// +// Cost summary with monthly and daily metrics. +type getEnvironmentEnvironmentCostCostSummary struct { + // Average monthly cost + MonthlyAverage getEnvironmentEnvironmentCostCostSummaryMonthlyAverageCostSample `json:"monthlyAverage"` + // Average daily cost + DailyAverage getEnvironmentEnvironmentCostCostSummaryDailyAverageCostSample `json:"dailyAverage"` +} + +// GetMonthlyAverage returns getEnvironmentEnvironmentCostCostSummary.MonthlyAverage, and is useful for accessing the field via an interface. +func (v *getEnvironmentEnvironmentCostCostSummary) GetMonthlyAverage() getEnvironmentEnvironmentCostCostSummaryMonthlyAverageCostSample { + return v.MonthlyAverage +} + +// GetDailyAverage returns getEnvironmentEnvironmentCostCostSummary.DailyAverage, and is useful for accessing the field via an interface. +func (v *getEnvironmentEnvironmentCostCostSummary) GetDailyAverage() getEnvironmentEnvironmentCostCostSummaryDailyAverageCostSample { + return v.DailyAverage +} + +// getEnvironmentEnvironmentCostCostSummaryDailyAverageCostSample includes the requested fields of the GraphQL type CostSample. +// The GraphQL type's documentation follows. +// +// A single cost measurement with amount and currency. +type getEnvironmentEnvironmentCostCostSummaryDailyAverageCostSample struct { + // The cost amount (null if no data available) + Amount float64 `json:"amount"` + // The currency code, e.g. USD (null if no data available) + Currency string `json:"currency"` +} + +// GetAmount returns getEnvironmentEnvironmentCostCostSummaryDailyAverageCostSample.Amount, and is useful for accessing the field via an interface. +func (v *getEnvironmentEnvironmentCostCostSummaryDailyAverageCostSample) GetAmount() float64 { + return v.Amount +} + +// GetCurrency returns getEnvironmentEnvironmentCostCostSummaryDailyAverageCostSample.Currency, and is useful for accessing the field via an interface. +func (v *getEnvironmentEnvironmentCostCostSummaryDailyAverageCostSample) GetCurrency() string { + return v.Currency +} + +// getEnvironmentEnvironmentCostCostSummaryMonthlyAverageCostSample includes the requested fields of the GraphQL type CostSample. +// The GraphQL type's documentation follows. +// +// A single cost measurement with amount and currency. +type getEnvironmentEnvironmentCostCostSummaryMonthlyAverageCostSample struct { + // The cost amount (null if no data available) + Amount float64 `json:"amount"` + // The currency code, e.g. USD (null if no data available) + Currency string `json:"currency"` +} + +// GetAmount returns getEnvironmentEnvironmentCostCostSummaryMonthlyAverageCostSample.Amount, and is useful for accessing the field via an interface. +func (v *getEnvironmentEnvironmentCostCostSummaryMonthlyAverageCostSample) GetAmount() float64 { + return v.Amount +} + +// GetCurrency returns getEnvironmentEnvironmentCostCostSummaryMonthlyAverageCostSample.Currency, and is useful for accessing the field via an interface. +func (v *getEnvironmentEnvironmentCostCostSummaryMonthlyAverageCostSample) GetCurrency() string { + return v.Currency +} + +// getEnvironmentEnvironmentProject includes the requested fields of the GraphQL type Project. +// The GraphQL type's documentation follows. +// +// A project. +type getEnvironmentEnvironmentProject struct { + Id string `json:"id"` + // Display name + Name string `json:"name"` + // What this project is for + Description string `json:"description"` +} + +// GetId returns getEnvironmentEnvironmentProject.Id, and is useful for accessing the field via an interface. +func (v *getEnvironmentEnvironmentProject) GetId() string { return v.Id } + +// GetName returns getEnvironmentEnvironmentProject.Name, and is useful for accessing the field via an interface. +func (v *getEnvironmentEnvironmentProject) GetName() string { return v.Name } + +// GetDescription returns getEnvironmentEnvironmentProject.Description, and is useful for accessing the field via an interface. +func (v *getEnvironmentEnvironmentProject) GetDescription() string { return v.Description } + +// getEnvironmentResponse is returned by getEnvironment on success. +type getEnvironmentResponse struct { + // Get a single environment by its ID. + Environment getEnvironmentEnvironment `json:"environment"` +} + +// GetEnvironment returns getEnvironmentResponse.Environment, and is useful for accessing the field via an interface. +func (v *getEnvironmentResponse) GetEnvironment() getEnvironmentEnvironment { return v.Environment } + +// getProjectProject includes the requested fields of the GraphQL type Project. +// The GraphQL type's documentation follows. +// +// A project. +type getProjectProject struct { + Id string `json:"id"` + // Display name + Name string `json:"name"` + // What this project is for + Description string `json:"description"` + // The environments in this project, like staging or production. + Environments getProjectProjectEnvironmentsEnvironmentsPage `json:"environments"` + // When this project was created (UTC) + CreatedAt time.Time `json:"createdAt"` + // When this project was last modified (UTC) + UpdatedAt time.Time `json:"updatedAt"` + // Whether this project can be deleted and any constraints preventing deletion. + Deletable getProjectProjectDeletable `json:"deletable"` + // Cloud provider costs for this project. + Cost getProjectProjectCostCostSummary `json:"cost"` +} + +// GetId returns getProjectProject.Id, and is useful for accessing the field via an interface. +func (v *getProjectProject) GetId() string { return v.Id } + +// GetName returns getProjectProject.Name, and is useful for accessing the field via an interface. +func (v *getProjectProject) GetName() string { return v.Name } + +// GetDescription returns getProjectProject.Description, and is useful for accessing the field via an interface. +func (v *getProjectProject) GetDescription() string { return v.Description } + +// GetEnvironments returns getProjectProject.Environments, and is useful for accessing the field via an interface. +func (v *getProjectProject) GetEnvironments() getProjectProjectEnvironmentsEnvironmentsPage { + return v.Environments +} + +// GetCreatedAt returns getProjectProject.CreatedAt, and is useful for accessing the field via an interface. +func (v *getProjectProject) GetCreatedAt() time.Time { return v.CreatedAt } + +// GetUpdatedAt returns getProjectProject.UpdatedAt, and is useful for accessing the field via an interface. +func (v *getProjectProject) GetUpdatedAt() time.Time { return v.UpdatedAt } + +// GetDeletable returns getProjectProject.Deletable, and is useful for accessing the field via an interface. +func (v *getProjectProject) GetDeletable() getProjectProjectDeletable { return v.Deletable } + +// GetCost returns getProjectProject.Cost, and is useful for accessing the field via an interface. +func (v *getProjectProject) GetCost() getProjectProjectCostCostSummary { return v.Cost } + +// getProjectProjectCostCostSummary includes the requested fields of the GraphQL type CostSummary. +// The GraphQL type's documentation follows. +// +// Cost summary with monthly and daily metrics. +type getProjectProjectCostCostSummary struct { + // Average monthly cost + MonthlyAverage getProjectProjectCostCostSummaryMonthlyAverageCostSample `json:"monthlyAverage"` + // Average daily cost + DailyAverage getProjectProjectCostCostSummaryDailyAverageCostSample `json:"dailyAverage"` +} + +// GetMonthlyAverage returns getProjectProjectCostCostSummary.MonthlyAverage, and is useful for accessing the field via an interface. +func (v *getProjectProjectCostCostSummary) GetMonthlyAverage() getProjectProjectCostCostSummaryMonthlyAverageCostSample { + return v.MonthlyAverage +} + +// GetDailyAverage returns getProjectProjectCostCostSummary.DailyAverage, and is useful for accessing the field via an interface. +func (v *getProjectProjectCostCostSummary) GetDailyAverage() getProjectProjectCostCostSummaryDailyAverageCostSample { + return v.DailyAverage +} + +// getProjectProjectCostCostSummaryDailyAverageCostSample includes the requested fields of the GraphQL type CostSample. +// The GraphQL type's documentation follows. +// +// A single cost measurement with amount and currency. +type getProjectProjectCostCostSummaryDailyAverageCostSample struct { + // The cost amount (null if no data available) + Amount float64 `json:"amount"` + // The currency code, e.g. USD (null if no data available) + Currency string `json:"currency"` +} + +// GetAmount returns getProjectProjectCostCostSummaryDailyAverageCostSample.Amount, and is useful for accessing the field via an interface. +func (v *getProjectProjectCostCostSummaryDailyAverageCostSample) GetAmount() float64 { return v.Amount } + +// GetCurrency returns getProjectProjectCostCostSummaryDailyAverageCostSample.Currency, and is useful for accessing the field via an interface. +func (v *getProjectProjectCostCostSummaryDailyAverageCostSample) GetCurrency() string { + return v.Currency +} + +// getProjectProjectCostCostSummaryMonthlyAverageCostSample includes the requested fields of the GraphQL type CostSample. +// The GraphQL type's documentation follows. +// +// A single cost measurement with amount and currency. +type getProjectProjectCostCostSummaryMonthlyAverageCostSample struct { + // The cost amount (null if no data available) + Amount float64 `json:"amount"` + // The currency code, e.g. USD (null if no data available) + Currency string `json:"currency"` +} + +// GetAmount returns getProjectProjectCostCostSummaryMonthlyAverageCostSample.Amount, and is useful for accessing the field via an interface. +func (v *getProjectProjectCostCostSummaryMonthlyAverageCostSample) GetAmount() float64 { + return v.Amount +} + +// GetCurrency returns getProjectProjectCostCostSummaryMonthlyAverageCostSample.Currency, and is useful for accessing the field via an interface. +func (v *getProjectProjectCostCostSummaryMonthlyAverageCostSample) GetCurrency() string { + return v.Currency +} + +// getProjectProjectDeletable includes the requested fields of the GraphQL type Deletable. +// The GraphQL type's documentation follows. +// +// Indicates whether a resource can be deleted and any constraints preventing deletion. +type getProjectProjectDeletable struct { + // Whether the resource can be deleted + Result bool `json:"result"` +} + +// GetResult returns getProjectProjectDeletable.Result, and is useful for accessing the field via an interface. +func (v *getProjectProjectDeletable) GetResult() bool { return v.Result } + +// getProjectProjectEnvironmentsEnvironmentsPage includes the requested fields of the GraphQL type EnvironmentsPage. +type getProjectProjectEnvironmentsEnvironmentsPage struct { + // A list of type environment. + Items []getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironment `json:"items"` +} + +// GetItems returns getProjectProjectEnvironmentsEnvironmentsPage.Items, and is useful for accessing the field via an interface. +func (v *getProjectProjectEnvironmentsEnvironmentsPage) GetItems() []getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironment { + return v.Items +} + +// getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironment includes the requested fields of the GraphQL type Environment. +// The GraphQL type's documentation follows. +// +// An environment. +type getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironment struct { + Id string `json:"id"` + // Display name + Name string `json:"name"` + // What this environment is for + Description string `json:"description"` + // When this environment was created (UTC) + CreatedAt time.Time `json:"createdAt"` + // When this environment was last modified (UTC) + UpdatedAt time.Time `json:"updatedAt"` + // Cloud provider costs for this environment. + Cost getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummary `json:"cost"` +} + +// GetId returns getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironment.Id, and is useful for accessing the field via an interface. +func (v *getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironment) GetId() string { return v.Id } + +// GetName returns getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironment.Name, and is useful for accessing the field via an interface. +func (v *getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironment) GetName() string { + return v.Name +} + +// GetDescription returns getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironment.Description, and is useful for accessing the field via an interface. +func (v *getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironment) GetDescription() string { + return v.Description +} + +// GetCreatedAt returns getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironment.CreatedAt, and is useful for accessing the field via an interface. +func (v *getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironment) GetCreatedAt() time.Time { + return v.CreatedAt +} + +// GetUpdatedAt returns getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironment.UpdatedAt, and is useful for accessing the field via an interface. +func (v *getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironment) GetUpdatedAt() time.Time { + return v.UpdatedAt +} + +// GetCost returns getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironment.Cost, and is useful for accessing the field via an interface. +func (v *getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironment) GetCost() getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummary { + return v.Cost +} + +// getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummary includes the requested fields of the GraphQL type CostSummary. +// The GraphQL type's documentation follows. +// +// Cost summary with monthly and daily metrics. +type getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummary struct { + // Average monthly cost + MonthlyAverage getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummaryMonthlyAverageCostSample `json:"monthlyAverage"` + // Average daily cost + DailyAverage getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummaryDailyAverageCostSample `json:"dailyAverage"` +} + +// GetMonthlyAverage returns getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummary.MonthlyAverage, and is useful for accessing the field via an interface. +func (v *getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummary) GetMonthlyAverage() getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummaryMonthlyAverageCostSample { + return v.MonthlyAverage +} + +// GetDailyAverage returns getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummary.DailyAverage, and is useful for accessing the field via an interface. +func (v *getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummary) GetDailyAverage() getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummaryDailyAverageCostSample { + return v.DailyAverage +} + +// getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummaryDailyAverageCostSample includes the requested fields of the GraphQL type CostSample. +// The GraphQL type's documentation follows. +// +// A single cost measurement with amount and currency. +type getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummaryDailyAverageCostSample struct { + // The cost amount (null if no data available) + Amount float64 `json:"amount"` + // The currency code, e.g. USD (null if no data available) + Currency string `json:"currency"` +} + +// GetAmount returns getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummaryDailyAverageCostSample.Amount, and is useful for accessing the field via an interface. +func (v *getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummaryDailyAverageCostSample) GetAmount() float64 { + return v.Amount +} + +// GetCurrency returns getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummaryDailyAverageCostSample.Currency, and is useful for accessing the field via an interface. +func (v *getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummaryDailyAverageCostSample) GetCurrency() string { + return v.Currency +} + +// getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummaryMonthlyAverageCostSample includes the requested fields of the GraphQL type CostSample. +// The GraphQL type's documentation follows. +// +// A single cost measurement with amount and currency. +type getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummaryMonthlyAverageCostSample struct { + // The cost amount (null if no data available) + Amount float64 `json:"amount"` + // The currency code, e.g. USD (null if no data available) + Currency string `json:"currency"` +} + +// GetAmount returns getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummaryMonthlyAverageCostSample.Amount, and is useful for accessing the field via an interface. +func (v *getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummaryMonthlyAverageCostSample) GetAmount() float64 { + return v.Amount +} + +// GetCurrency returns getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummaryMonthlyAverageCostSample.Currency, and is useful for accessing the field via an interface. +func (v *getProjectProjectEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummaryMonthlyAverageCostSample) GetCurrency() string { + return v.Currency +} + +// getProjectResponse is returned by getProject on success. +type getProjectResponse struct { + // Get a single project by its ID. + Project getProjectProject `json:"project"` +} + +// GetProject returns getProjectResponse.Project, and is useful for accessing the field via an interface. +func (v *getProjectResponse) GetProject() getProjectProject { return v.Project } + +// listEnvironmentsEnvironmentsEnvironmentsPage includes the requested fields of the GraphQL type EnvironmentsPage. +type listEnvironmentsEnvironmentsEnvironmentsPage struct { + // Pagination cursors + Cursor listEnvironmentsEnvironmentsEnvironmentsPageCursorPaginationCursor `json:"cursor"` + // A list of type environment. + Items []listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironment `json:"items"` +} + +// GetCursor returns listEnvironmentsEnvironmentsEnvironmentsPage.Cursor, and is useful for accessing the field via an interface. +func (v *listEnvironmentsEnvironmentsEnvironmentsPage) GetCursor() listEnvironmentsEnvironmentsEnvironmentsPageCursorPaginationCursor { + return v.Cursor +} + +// GetItems returns listEnvironmentsEnvironmentsEnvironmentsPage.Items, and is useful for accessing the field via an interface. +func (v *listEnvironmentsEnvironmentsEnvironmentsPage) GetItems() []listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironment { + return v.Items +} + +// listEnvironmentsEnvironmentsEnvironmentsPageCursorPaginationCursor includes the requested fields of the GraphQL type PaginationCursor. +type listEnvironmentsEnvironmentsEnvironmentsPageCursorPaginationCursor struct { + // Cursor to the next page + Next string `json:"next"` + // Cursor to the previous page + Previous string `json:"previous"` +} + +// GetNext returns listEnvironmentsEnvironmentsEnvironmentsPageCursorPaginationCursor.Next, and is useful for accessing the field via an interface. +func (v *listEnvironmentsEnvironmentsEnvironmentsPageCursorPaginationCursor) GetNext() string { + return v.Next +} + +// GetPrevious returns listEnvironmentsEnvironmentsEnvironmentsPageCursorPaginationCursor.Previous, and is useful for accessing the field via an interface. +func (v *listEnvironmentsEnvironmentsEnvironmentsPageCursorPaginationCursor) GetPrevious() string { + return v.Previous +} + +// listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironment includes the requested fields of the GraphQL type Environment. +// The GraphQL type's documentation follows. +// +// An environment. +type listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironment struct { + Id string `json:"id"` + // Display name + Name string `json:"name"` + // What this environment is for + Description string `json:"description"` + // When this environment was created (UTC) + CreatedAt time.Time `json:"createdAt"` + // When this environment was last modified (UTC) + UpdatedAt time.Time `json:"updatedAt"` + // The project containing this environment. + Project listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironmentProject `json:"project"` + // Cloud provider costs for this environment. + Cost listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummary `json:"cost"` +} + +// GetId returns listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironment.Id, and is useful for accessing the field via an interface. +func (v *listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironment) GetId() string { return v.Id } + +// GetName returns listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironment.Name, and is useful for accessing the field via an interface. +func (v *listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironment) GetName() string { + return v.Name +} + +// GetDescription returns listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironment.Description, and is useful for accessing the field via an interface. +func (v *listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironment) GetDescription() string { + return v.Description +} + +// GetCreatedAt returns listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironment.CreatedAt, and is useful for accessing the field via an interface. +func (v *listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironment) GetCreatedAt() time.Time { + return v.CreatedAt +} + +// GetUpdatedAt returns listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironment.UpdatedAt, and is useful for accessing the field via an interface. +func (v *listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironment) GetUpdatedAt() time.Time { + return v.UpdatedAt +} + +// GetProject returns listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironment.Project, and is useful for accessing the field via an interface. +func (v *listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironment) GetProject() listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironmentProject { + return v.Project +} + +// GetCost returns listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironment.Cost, and is useful for accessing the field via an interface. +func (v *listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironment) GetCost() listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummary { + return v.Cost +} + +// listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummary includes the requested fields of the GraphQL type CostSummary. +// The GraphQL type's documentation follows. +// +// Cost summary with monthly and daily metrics. +type listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummary struct { + // Average monthly cost + MonthlyAverage listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummaryMonthlyAverageCostSample `json:"monthlyAverage"` + // Average daily cost + DailyAverage listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummaryDailyAverageCostSample `json:"dailyAverage"` +} + +// GetMonthlyAverage returns listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummary.MonthlyAverage, and is useful for accessing the field via an interface. +func (v *listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummary) GetMonthlyAverage() listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummaryMonthlyAverageCostSample { + return v.MonthlyAverage +} + +// GetDailyAverage returns listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummary.DailyAverage, and is useful for accessing the field via an interface. +func (v *listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummary) GetDailyAverage() listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummaryDailyAverageCostSample { + return v.DailyAverage +} + +// listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummaryDailyAverageCostSample includes the requested fields of the GraphQL type CostSample. +// The GraphQL type's documentation follows. +// +// A single cost measurement with amount and currency. +type listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummaryDailyAverageCostSample struct { + // The cost amount (null if no data available) + Amount float64 `json:"amount"` + // The currency code, e.g. USD (null if no data available) + Currency string `json:"currency"` +} + +// GetAmount returns listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummaryDailyAverageCostSample.Amount, and is useful for accessing the field via an interface. +func (v *listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummaryDailyAverageCostSample) GetAmount() float64 { + return v.Amount +} + +// GetCurrency returns listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummaryDailyAverageCostSample.Currency, and is useful for accessing the field via an interface. +func (v *listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummaryDailyAverageCostSample) GetCurrency() string { + return v.Currency +} + +// listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummaryMonthlyAverageCostSample includes the requested fields of the GraphQL type CostSample. +// The GraphQL type's documentation follows. +// +// A single cost measurement with amount and currency. +type listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummaryMonthlyAverageCostSample struct { + // The cost amount (null if no data available) + Amount float64 `json:"amount"` + // The currency code, e.g. USD (null if no data available) + Currency string `json:"currency"` +} + +// GetAmount returns listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummaryMonthlyAverageCostSample.Amount, and is useful for accessing the field via an interface. +func (v *listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummaryMonthlyAverageCostSample) GetAmount() float64 { + return v.Amount +} + +// GetCurrency returns listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummaryMonthlyAverageCostSample.Currency, and is useful for accessing the field via an interface. +func (v *listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironmentCostCostSummaryMonthlyAverageCostSample) GetCurrency() string { + return v.Currency +} + +// listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironmentProject includes the requested fields of the GraphQL type Project. +// The GraphQL type's documentation follows. +// +// A project. +type listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironmentProject struct { + Id string `json:"id"` + // Display name + Name string `json:"name"` +} + +// GetId returns listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironmentProject.Id, and is useful for accessing the field via an interface. +func (v *listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironmentProject) GetId() string { + return v.Id +} + +// GetName returns listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironmentProject.Name, and is useful for accessing the field via an interface. +func (v *listEnvironmentsEnvironmentsEnvironmentsPageItemsEnvironmentProject) GetName() string { + return v.Name +} + +// listEnvironmentsResponse is returned by listEnvironments on success. +type listEnvironmentsResponse struct { + // List all environments you have access to. Returns a paginated list sorted by name. + Environments listEnvironmentsEnvironmentsEnvironmentsPage `json:"environments"` +} + +// GetEnvironments returns listEnvironmentsResponse.Environments, and is useful for accessing the field via an interface. +func (v *listEnvironmentsResponse) GetEnvironments() listEnvironmentsEnvironmentsEnvironmentsPage { + return v.Environments +} + +// listProjectsProjectsProjectsPage includes the requested fields of the GraphQL type ProjectsPage. +type listProjectsProjectsProjectsPage struct { + // A list of type project. + Items []listProjectsProjectsProjectsPageItemsProject `json:"items"` +} + +// GetItems returns listProjectsProjectsProjectsPage.Items, and is useful for accessing the field via an interface. +func (v *listProjectsProjectsProjectsPage) GetItems() []listProjectsProjectsProjectsPageItemsProject { + return v.Items +} + +// listProjectsProjectsProjectsPageItemsProject includes the requested fields of the GraphQL type Project. +// The GraphQL type's documentation follows. +// +// A project. +type listProjectsProjectsProjectsPageItemsProject struct { + Id string `json:"id"` + // Display name + Name string `json:"name"` + // What this project is for + Description string `json:"description"` + // When this project was created (UTC) + CreatedAt time.Time `json:"createdAt"` + // When this project was last modified (UTC) + UpdatedAt time.Time `json:"updatedAt"` + // Cloud provider costs for this project. + Cost listProjectsProjectsProjectsPageItemsProjectCostCostSummary `json:"cost"` +} + +// GetId returns listProjectsProjectsProjectsPageItemsProject.Id, and is useful for accessing the field via an interface. +func (v *listProjectsProjectsProjectsPageItemsProject) GetId() string { return v.Id } + +// GetName returns listProjectsProjectsProjectsPageItemsProject.Name, and is useful for accessing the field via an interface. +func (v *listProjectsProjectsProjectsPageItemsProject) GetName() string { return v.Name } + +// GetDescription returns listProjectsProjectsProjectsPageItemsProject.Description, and is useful for accessing the field via an interface. +func (v *listProjectsProjectsProjectsPageItemsProject) GetDescription() string { return v.Description } + +// GetCreatedAt returns listProjectsProjectsProjectsPageItemsProject.CreatedAt, and is useful for accessing the field via an interface. +func (v *listProjectsProjectsProjectsPageItemsProject) GetCreatedAt() time.Time { return v.CreatedAt } + +// GetUpdatedAt returns listProjectsProjectsProjectsPageItemsProject.UpdatedAt, and is useful for accessing the field via an interface. +func (v *listProjectsProjectsProjectsPageItemsProject) GetUpdatedAt() time.Time { return v.UpdatedAt } + +// GetCost returns listProjectsProjectsProjectsPageItemsProject.Cost, and is useful for accessing the field via an interface. +func (v *listProjectsProjectsProjectsPageItemsProject) GetCost() listProjectsProjectsProjectsPageItemsProjectCostCostSummary { + return v.Cost +} + +// listProjectsProjectsProjectsPageItemsProjectCostCostSummary includes the requested fields of the GraphQL type CostSummary. +// The GraphQL type's documentation follows. +// +// Cost summary with monthly and daily metrics. +type listProjectsProjectsProjectsPageItemsProjectCostCostSummary struct { + // Average monthly cost + MonthlyAverage listProjectsProjectsProjectsPageItemsProjectCostCostSummaryMonthlyAverageCostSample `json:"monthlyAverage"` + // Average daily cost + DailyAverage listProjectsProjectsProjectsPageItemsProjectCostCostSummaryDailyAverageCostSample `json:"dailyAverage"` +} + +// GetMonthlyAverage returns listProjectsProjectsProjectsPageItemsProjectCostCostSummary.MonthlyAverage, and is useful for accessing the field via an interface. +func (v *listProjectsProjectsProjectsPageItemsProjectCostCostSummary) GetMonthlyAverage() listProjectsProjectsProjectsPageItemsProjectCostCostSummaryMonthlyAverageCostSample { + return v.MonthlyAverage +} + +// GetDailyAverage returns listProjectsProjectsProjectsPageItemsProjectCostCostSummary.DailyAverage, and is useful for accessing the field via an interface. +func (v *listProjectsProjectsProjectsPageItemsProjectCostCostSummary) GetDailyAverage() listProjectsProjectsProjectsPageItemsProjectCostCostSummaryDailyAverageCostSample { + return v.DailyAverage +} + +// listProjectsProjectsProjectsPageItemsProjectCostCostSummaryDailyAverageCostSample includes the requested fields of the GraphQL type CostSample. +// The GraphQL type's documentation follows. +// +// A single cost measurement with amount and currency. +type listProjectsProjectsProjectsPageItemsProjectCostCostSummaryDailyAverageCostSample struct { + // The cost amount (null if no data available) + Amount float64 `json:"amount"` + // The currency code, e.g. USD (null if no data available) + Currency string `json:"currency"` +} + +// GetAmount returns listProjectsProjectsProjectsPageItemsProjectCostCostSummaryDailyAverageCostSample.Amount, and is useful for accessing the field via an interface. +func (v *listProjectsProjectsProjectsPageItemsProjectCostCostSummaryDailyAverageCostSample) GetAmount() float64 { + return v.Amount +} + +// GetCurrency returns listProjectsProjectsProjectsPageItemsProjectCostCostSummaryDailyAverageCostSample.Currency, and is useful for accessing the field via an interface. +func (v *listProjectsProjectsProjectsPageItemsProjectCostCostSummaryDailyAverageCostSample) GetCurrency() string { + return v.Currency +} + +// listProjectsProjectsProjectsPageItemsProjectCostCostSummaryMonthlyAverageCostSample includes the requested fields of the GraphQL type CostSample. +// The GraphQL type's documentation follows. +// +// A single cost measurement with amount and currency. +type listProjectsProjectsProjectsPageItemsProjectCostCostSummaryMonthlyAverageCostSample struct { + // The cost amount (null if no data available) + Amount float64 `json:"amount"` + // The currency code, e.g. USD (null if no data available) + Currency string `json:"currency"` +} + +// GetAmount returns listProjectsProjectsProjectsPageItemsProjectCostCostSummaryMonthlyAverageCostSample.Amount, and is useful for accessing the field via an interface. +func (v *listProjectsProjectsProjectsPageItemsProjectCostCostSummaryMonthlyAverageCostSample) GetAmount() float64 { + return v.Amount +} + +// GetCurrency returns listProjectsProjectsProjectsPageItemsProjectCostCostSummaryMonthlyAverageCostSample.Currency, and is useful for accessing the field via an interface. +func (v *listProjectsProjectsProjectsPageItemsProjectCostCostSummaryMonthlyAverageCostSample) GetCurrency() string { + return v.Currency +} + +// listProjectsResponse is returned by listProjects on success. +type listProjectsResponse struct { + // List all projects you have access to. Returns a paginated list sorted by name. + Projects listProjectsProjectsProjectsPage `json:"projects"` +} + +// GetProjects returns listProjectsResponse.Projects, and is useful for accessing the field via an interface. +func (v *listProjectsResponse) GetProjects() listProjectsProjectsProjectsPage { return v.Projects } + +// updateEnvironmentResponse is returned by updateEnvironment on success. +type updateEnvironmentResponse struct { + // Update an environment's name or description. + UpdateEnvironment updateEnvironmentUpdateEnvironmentEnvironmentPayload `json:"updateEnvironment"` +} + +// GetUpdateEnvironment returns updateEnvironmentResponse.UpdateEnvironment, and is useful for accessing the field via an interface. +func (v *updateEnvironmentResponse) GetUpdateEnvironment() updateEnvironmentUpdateEnvironmentEnvironmentPayload { + return v.UpdateEnvironment +} + +// updateEnvironmentUpdateEnvironmentEnvironmentPayload includes the requested fields of the GraphQL type EnvironmentPayload. +type updateEnvironmentUpdateEnvironmentEnvironmentPayload struct { + // The object created/updated/deleted by the mutation. May be null if mutation failed. + Result updateEnvironmentUpdateEnvironmentEnvironmentPayloadResultEnvironment `json:"result"` + // Indicates if the mutation completed successfully or not. + Successful bool `json:"successful"` + // A list of failed validations. May be blank or null if mutation succeeded. + Messages []updateEnvironmentUpdateEnvironmentEnvironmentPayloadMessagesValidationMessage `json:"messages"` +} + +// GetResult returns updateEnvironmentUpdateEnvironmentEnvironmentPayload.Result, and is useful for accessing the field via an interface. +func (v *updateEnvironmentUpdateEnvironmentEnvironmentPayload) GetResult() updateEnvironmentUpdateEnvironmentEnvironmentPayloadResultEnvironment { + return v.Result +} + +// GetSuccessful returns updateEnvironmentUpdateEnvironmentEnvironmentPayload.Successful, and is useful for accessing the field via an interface. +func (v *updateEnvironmentUpdateEnvironmentEnvironmentPayload) GetSuccessful() bool { + return v.Successful +} + +// GetMessages returns updateEnvironmentUpdateEnvironmentEnvironmentPayload.Messages, and is useful for accessing the field via an interface. +func (v *updateEnvironmentUpdateEnvironmentEnvironmentPayload) GetMessages() []updateEnvironmentUpdateEnvironmentEnvironmentPayloadMessagesValidationMessage { + return v.Messages +} + +// updateEnvironmentUpdateEnvironmentEnvironmentPayloadMessagesValidationMessage includes the requested fields of the GraphQL type ValidationMessage. +// The GraphQL type's documentation follows. +// +// Validation messages are returned when mutation input does not meet the requirements. +// While client-side validation is highly recommended to provide the best User Experience, +// All inputs will always be validated server-side. +// +// Some examples of validations are: +// +// * Username must be at least 10 characters +// * Email field does not contain an email address +// * Birth Date is required +// +// While GraphQL has support for required values, mutation data fields are always +// set to optional in our API. This allows 'required field' messages +// to be returned in the same manner as other validations. The only exceptions +// are id fields, which may be required to perform updates or deletes. +type updateEnvironmentUpdateEnvironmentEnvironmentPayloadMessagesValidationMessage struct { + // A unique error code for the type of validation used. + Code string `json:"code"` + // The input field that the error applies to. The field can be used to + // identify which field the error message should be displayed next to in the + // presentation layer. + // + // If there are multiple errors to display for a field, multiple validation + // messages will be in the result. + // + // This field may be null in cases where an error cannot be applied to a specific field. + Field string `json:"field"` + // A friendly error message, appropriate for display to the end user. + // + // The message is interpolated to include the appropriate variables. + // + // Example: `Username must be at least 10 characters` + // + // This message may change without notice, so we do not recommend you match against the text. + // Instead, use the *code* field for matching. + Message string `json:"message"` +} + +// GetCode returns updateEnvironmentUpdateEnvironmentEnvironmentPayloadMessagesValidationMessage.Code, and is useful for accessing the field via an interface. +func (v *updateEnvironmentUpdateEnvironmentEnvironmentPayloadMessagesValidationMessage) GetCode() string { + return v.Code +} + +// GetField returns updateEnvironmentUpdateEnvironmentEnvironmentPayloadMessagesValidationMessage.Field, and is useful for accessing the field via an interface. +func (v *updateEnvironmentUpdateEnvironmentEnvironmentPayloadMessagesValidationMessage) GetField() string { + return v.Field +} + +// GetMessage returns updateEnvironmentUpdateEnvironmentEnvironmentPayloadMessagesValidationMessage.Message, and is useful for accessing the field via an interface. +func (v *updateEnvironmentUpdateEnvironmentEnvironmentPayloadMessagesValidationMessage) GetMessage() string { + return v.Message +} + +// updateEnvironmentUpdateEnvironmentEnvironmentPayloadResultEnvironment includes the requested fields of the GraphQL type Environment. +// The GraphQL type's documentation follows. +// +// An environment. +type updateEnvironmentUpdateEnvironmentEnvironmentPayloadResultEnvironment struct { + Id string `json:"id"` + // Display name + Name string `json:"name"` + // What this environment is for + Description string `json:"description"` +} + +// GetId returns updateEnvironmentUpdateEnvironmentEnvironmentPayloadResultEnvironment.Id, and is useful for accessing the field via an interface. +func (v *updateEnvironmentUpdateEnvironmentEnvironmentPayloadResultEnvironment) GetId() string { + return v.Id +} + +// GetName returns updateEnvironmentUpdateEnvironmentEnvironmentPayloadResultEnvironment.Name, and is useful for accessing the field via an interface. +func (v *updateEnvironmentUpdateEnvironmentEnvironmentPayloadResultEnvironment) GetName() string { + return v.Name +} + +// GetDescription returns updateEnvironmentUpdateEnvironmentEnvironmentPayloadResultEnvironment.Description, and is useful for accessing the field via an interface. +func (v *updateEnvironmentUpdateEnvironmentEnvironmentPayloadResultEnvironment) GetDescription() string { + return v.Description +} + +// updateProjectResponse is returned by updateProject on success. +type updateProjectResponse struct { + // Update a project's name or description. + UpdateProject updateProjectUpdateProjectProjectPayload `json:"updateProject"` +} + +// GetUpdateProject returns updateProjectResponse.UpdateProject, and is useful for accessing the field via an interface. +func (v *updateProjectResponse) GetUpdateProject() updateProjectUpdateProjectProjectPayload { + return v.UpdateProject +} + +// updateProjectUpdateProjectProjectPayload includes the requested fields of the GraphQL type ProjectPayload. +type updateProjectUpdateProjectProjectPayload struct { + // The object created/updated/deleted by the mutation. May be null if mutation failed. + Result updateProjectUpdateProjectProjectPayloadResultProject `json:"result"` + // Indicates if the mutation completed successfully or not. + Successful bool `json:"successful"` + // A list of failed validations. May be blank or null if mutation succeeded. + Messages []updateProjectUpdateProjectProjectPayloadMessagesValidationMessage `json:"messages"` +} + +// GetResult returns updateProjectUpdateProjectProjectPayload.Result, and is useful for accessing the field via an interface. +func (v *updateProjectUpdateProjectProjectPayload) GetResult() updateProjectUpdateProjectProjectPayloadResultProject { + return v.Result +} + +// GetSuccessful returns updateProjectUpdateProjectProjectPayload.Successful, and is useful for accessing the field via an interface. +func (v *updateProjectUpdateProjectProjectPayload) GetSuccessful() bool { return v.Successful } + +// GetMessages returns updateProjectUpdateProjectProjectPayload.Messages, and is useful for accessing the field via an interface. +func (v *updateProjectUpdateProjectProjectPayload) GetMessages() []updateProjectUpdateProjectProjectPayloadMessagesValidationMessage { + return v.Messages +} + +// updateProjectUpdateProjectProjectPayloadMessagesValidationMessage includes the requested fields of the GraphQL type ValidationMessage. +// The GraphQL type's documentation follows. +// +// Validation messages are returned when mutation input does not meet the requirements. +// While client-side validation is highly recommended to provide the best User Experience, +// All inputs will always be validated server-side. +// +// Some examples of validations are: +// +// * Username must be at least 10 characters +// * Email field does not contain an email address +// * Birth Date is required +// +// While GraphQL has support for required values, mutation data fields are always +// set to optional in our API. This allows 'required field' messages +// to be returned in the same manner as other validations. The only exceptions +// are id fields, which may be required to perform updates or deletes. +type updateProjectUpdateProjectProjectPayloadMessagesValidationMessage struct { + // A unique error code for the type of validation used. + Code string `json:"code"` + // The input field that the error applies to. The field can be used to + // identify which field the error message should be displayed next to in the + // presentation layer. + // + // If there are multiple errors to display for a field, multiple validation + // messages will be in the result. + // + // This field may be null in cases where an error cannot be applied to a specific field. + Field string `json:"field"` + // A friendly error message, appropriate for display to the end user. + // + // The message is interpolated to include the appropriate variables. + // + // Example: `Username must be at least 10 characters` + // + // This message may change without notice, so we do not recommend you match against the text. + // Instead, use the *code* field for matching. + Message string `json:"message"` +} + +// GetCode returns updateProjectUpdateProjectProjectPayloadMessagesValidationMessage.Code, and is useful for accessing the field via an interface. +func (v *updateProjectUpdateProjectProjectPayloadMessagesValidationMessage) GetCode() string { + return v.Code +} + +// GetField returns updateProjectUpdateProjectProjectPayloadMessagesValidationMessage.Field, and is useful for accessing the field via an interface. +func (v *updateProjectUpdateProjectProjectPayloadMessagesValidationMessage) GetField() string { + return v.Field +} + +// GetMessage returns updateProjectUpdateProjectProjectPayloadMessagesValidationMessage.Message, and is useful for accessing the field via an interface. +func (v *updateProjectUpdateProjectProjectPayloadMessagesValidationMessage) GetMessage() string { + return v.Message +} + +// updateProjectUpdateProjectProjectPayloadResultProject includes the requested fields of the GraphQL type Project. +// The GraphQL type's documentation follows. +// +// A project. +type updateProjectUpdateProjectProjectPayloadResultProject struct { + Id string `json:"id"` + // Display name + Name string `json:"name"` + // What this project is for + Description string `json:"description"` +} + +// GetId returns updateProjectUpdateProjectProjectPayloadResultProject.Id, and is useful for accessing the field via an interface. +func (v *updateProjectUpdateProjectProjectPayloadResultProject) GetId() string { return v.Id } + +// GetName returns updateProjectUpdateProjectProjectPayloadResultProject.Name, and is useful for accessing the field via an interface. +func (v *updateProjectUpdateProjectProjectPayloadResultProject) GetName() string { return v.Name } + +// GetDescription returns updateProjectUpdateProjectProjectPayloadResultProject.Description, and is useful for accessing the field via an interface. +func (v *updateProjectUpdateProjectProjectPayloadResultProject) GetDescription() string { + return v.Description +} + +// The mutation executed by createEnvironment. +const createEnvironment_Operation = ` +mutation createEnvironment ($organizationId: ID!, $projectId: ID!, $input: CreateEnvironmentInput!) { + createEnvironment(organizationId: $organizationId, projectId: $projectId, input: $input) { + result { + id + name + description + } + successful + messages { + code + field + message + } + } +} +` + +func createEnvironment( + ctx_ context.Context, + client_ graphql.Client, + organizationId string, + projectId string, + input CreateEnvironmentInput, +) (data_ *createEnvironmentResponse, err_ error) { + req_ := &graphql.Request{ + OpName: "createEnvironment", + Query: createEnvironment_Operation, + Variables: &__createEnvironmentInput{ + OrganizationId: organizationId, + ProjectId: projectId, + Input: input, + }, + } + + data_ = &createEnvironmentResponse{} + resp_ := &graphql.Response{Data: data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return data_, err_ +} + +// The mutation executed by createProject. +const createProject_Operation = ` +mutation createProject ($organizationId: ID!, $input: CreateProjectInput!) { + createProject(organizationId: $organizationId, input: $input) { + result { + id + name + description + } + successful + messages { + code + field + message + } + } +} +` + +func createProject( + ctx_ context.Context, + client_ graphql.Client, + organizationId string, + input CreateProjectInput, +) (data_ *createProjectResponse, err_ error) { + req_ := &graphql.Request{ + OpName: "createProject", + Query: createProject_Operation, + Variables: &__createProjectInput{ + OrganizationId: organizationId, + Input: input, + }, + } + + data_ = &createProjectResponse{} + resp_ := &graphql.Response{Data: data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return data_, err_ +} + +// The mutation executed by deleteEnvironment. +const deleteEnvironment_Operation = ` +mutation deleteEnvironment ($organizationId: ID!, $id: ID!) { + deleteEnvironment(organizationId: $organizationId, id: $id) { + result { + id + name + description + } + successful + messages { + code + field + message + } + } +} +` + +func deleteEnvironment( + ctx_ context.Context, + client_ graphql.Client, + organizationId string, + id string, +) (data_ *deleteEnvironmentResponse, err_ error) { + req_ := &graphql.Request{ + OpName: "deleteEnvironment", + Query: deleteEnvironment_Operation, + Variables: &__deleteEnvironmentInput{ + OrganizationId: organizationId, + Id: id, + }, + } + + data_ = &deleteEnvironmentResponse{} + resp_ := &graphql.Response{Data: data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return data_, err_ +} + +// The mutation executed by deleteProject. +const deleteProject_Operation = ` +mutation deleteProject ($organizationId: ID!, $id: ID!) { + deleteProject(organizationId: $organizationId, id: $id) { + result { + id + name + description + } + successful + messages { + code + field + message + } + } +} +` + +func deleteProject( + ctx_ context.Context, + client_ graphql.Client, + organizationId string, + id string, +) (data_ *deleteProjectResponse, err_ error) { + req_ := &graphql.Request{ + OpName: "deleteProject", + Query: deleteProject_Operation, + Variables: &__deleteProjectInput{ + OrganizationId: organizationId, + Id: id, + }, + } + + data_ = &deleteProjectResponse{} + resp_ := &graphql.Response{Data: data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return data_, err_ +} + +// The query executed by getEnvironment. +const getEnvironment_Operation = ` +query getEnvironment ($organizationId: ID!, $id: ID!) { + environment(organizationId: $organizationId, id: $id) { + id + name + description + createdAt + updatedAt + cost { + monthlyAverage { + amount + currency + } + dailyAverage { + amount + currency + } + } + project { + id + name + description + } + blueprint { + instances { + cursor { + next + previous + } + items { + id + name + status + version + releaseStrategy + createdAt + updatedAt + bundle { + name + id + } + } + } + } + } +} +` + +func getEnvironment( + ctx_ context.Context, + client_ graphql.Client, + organizationId string, + id string, +) (data_ *getEnvironmentResponse, err_ error) { + req_ := &graphql.Request{ + OpName: "getEnvironment", + Query: getEnvironment_Operation, + Variables: &__getEnvironmentInput{ + OrganizationId: organizationId, + Id: id, + }, + } + + data_ = &getEnvironmentResponse{} + resp_ := &graphql.Response{Data: data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return data_, err_ +} + +// The query executed by getProject. +const getProject_Operation = ` +query getProject ($organizationId: ID!, $id: ID!) { + project(organizationId: $organizationId, id: $id) { + id + name + description + environments { + items { + id + name + description + createdAt + updatedAt + cost { + monthlyAverage { + amount + currency + } + dailyAverage { + amount + currency + } + } + } + } + createdAt + updatedAt + deletable { + result + } + cost { + monthlyAverage { + amount + currency + } + dailyAverage { + amount + currency + } + } + } +} +` + +func getProject( + ctx_ context.Context, + client_ graphql.Client, + organizationId string, + id string, +) (data_ *getProjectResponse, err_ error) { + req_ := &graphql.Request{ + OpName: "getProject", + Query: getProject_Operation, + Variables: &__getProjectInput{ + OrganizationId: organizationId, + Id: id, + }, + } + + data_ = &getProjectResponse{} + resp_ := &graphql.Response{Data: data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return data_, err_ +} + +// The query executed by listEnvironments. +const listEnvironments_Operation = ` +query listEnvironments ($organizationId: ID!, $filter: EnvironmentsFilter, $sort: EnvironmentsSort, $cursor: Cursor) { + environments(organizationId: $organizationId, filter: $filter, sort: $sort, cursor: $cursor) { + cursor { + next + previous + } + items { + id + name + description + createdAt + updatedAt + project { + id + name + } + cost { + monthlyAverage { + amount + currency + } + dailyAverage { + amount + currency + } + } + } + } +} +` + +func listEnvironments( + ctx_ context.Context, + client_ graphql.Client, + organizationId string, + filter *EnvironmentsFilter, + sort *EnvironmentsSort, + cursor *Cursor, +) (data_ *listEnvironmentsResponse, err_ error) { + req_ := &graphql.Request{ + OpName: "listEnvironments", + Query: listEnvironments_Operation, + Variables: &__listEnvironmentsInput{ + OrganizationId: organizationId, + Filter: filter, + Sort: sort, + Cursor: cursor, + }, + } + + data_ = &listEnvironmentsResponse{} + resp_ := &graphql.Response{Data: data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return data_, err_ +} + +// The query executed by listProjects. +const listProjects_Operation = ` +query listProjects ($organizationId: ID!) { + projects(organizationId: $organizationId, sort: {field:NAME,order:ASC}) { + items { + id + name + description + createdAt + updatedAt + cost { + monthlyAverage { + amount + currency + } + dailyAverage { + amount + currency + } + } + } + } +} +` + +func listProjects( + ctx_ context.Context, + client_ graphql.Client, + organizationId string, +) (data_ *listProjectsResponse, err_ error) { + req_ := &graphql.Request{ + OpName: "listProjects", + Query: listProjects_Operation, + Variables: &__listProjectsInput{ + OrganizationId: organizationId, + }, + } + + data_ = &listProjectsResponse{} + resp_ := &graphql.Response{Data: data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return data_, err_ +} + +// The mutation executed by updateEnvironment. +const updateEnvironment_Operation = ` +mutation updateEnvironment ($organizationId: ID!, $id: ID!, $input: UpdateEnvironmentInput!) { + updateEnvironment(organizationId: $organizationId, id: $id, input: $input) { + result { + id + name + description + } + successful + messages { + code + field + message + } + } +} +` + +func updateEnvironment( + ctx_ context.Context, + client_ graphql.Client, + organizationId string, + id string, + input UpdateEnvironmentInput, +) (data_ *updateEnvironmentResponse, err_ error) { + req_ := &graphql.Request{ + OpName: "updateEnvironment", + Query: updateEnvironment_Operation, + Variables: &__updateEnvironmentInput{ + OrganizationId: organizationId, + Id: id, + Input: input, + }, + } + + data_ = &updateEnvironmentResponse{} + resp_ := &graphql.Response{Data: data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return data_, err_ +} + +// The mutation executed by updateProject. +const updateProject_Operation = ` +mutation updateProject ($organizationId: ID!, $id: ID!, $input: UpdateProjectInput!) { + updateProject(organizationId: $organizationId, id: $id, input: $input) { + result { + id + name + description + } + successful + messages { + code + field + message + } + } +} +` + +func updateProject( + ctx_ context.Context, + client_ graphql.Client, + organizationId string, + id string, + input UpdateProjectInput, +) (data_ *updateProjectResponse, err_ error) { + req_ := &graphql.Request{ + OpName: "updateProject", + Query: updateProject_Operation, + Variables: &__updateProjectInput{ + OrganizationId: organizationId, + Id: id, + Input: input, + }, + } + + data_ = &updateProjectResponse{} + resp_ := &graphql.Response{Data: data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return data_, err_ +} diff --git a/internal/artifact/import_prompt.go b/internal/artifact/import_prompt.go index dd955b95..099aeb85 100644 --- a/internal/artifact/import_prompt.go +++ b/internal/artifact/import_prompt.go @@ -7,7 +7,7 @@ import ( "regexp" "sort" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/AlecAivazis/survey/v2" "github.com/manifoldco/promptui" diff --git a/internal/commands/artifact/import.go b/internal/commands/artifact/import.go index ddbd13ce..94dcc9ea 100644 --- a/internal/commands/artifact/import.go +++ b/internal/commands/artifact/import.go @@ -7,7 +7,7 @@ import ( "fmt" "os" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/artifact" "github.com/massdriver-cloud/mass/internal/jsonschema" diff --git a/internal/commands/artifact/update.go b/internal/commands/artifact/update.go index 4638919f..0880a397 100644 --- a/internal/commands/artifact/update.go +++ b/internal/commands/artifact/update.go @@ -6,7 +6,7 @@ import ( "fmt" "os" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" artifactpkg "github.com/massdriver-cloud/mass/internal/artifact" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" diff --git a/internal/commands/bundle/publish.go b/internal/commands/bundle/publish.go index b6f0dc41..27130894 100644 --- a/internal/commands/bundle/publish.go +++ b/internal/commands/bundle/publish.go @@ -6,7 +6,7 @@ import ( "slices" "time" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/bundle" "github.com/massdriver-cloud/mass/internal/prettylogs" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" diff --git a/internal/commands/bundle/pull.go b/internal/commands/bundle/pull.go index a9c7577d..ad069a35 100644 --- a/internal/commands/bundle/pull.go +++ b/internal/commands/bundle/pull.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/bundle" "github.com/massdriver-cloud/mass/internal/prettylogs" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" diff --git a/internal/commands/environment/export.go b/internal/commands/environment/export.go index 958b22b1..0afa4645 100644 --- a/internal/commands/environment/export.go +++ b/internal/commands/environment/export.go @@ -7,9 +7,8 @@ import ( "fmt" "path/filepath" - "github.com/massdriver-cloud/mass/internal/api" - "github.com/massdriver-cloud/mass/internal/commands/pkg" - + "github.com/massdriver-cloud/mass/internal/api/v0" + "github.com/massdriver-cloud/mass/internal/commands/instance" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" ) @@ -32,7 +31,7 @@ func ExportEnvironment(ctx context.Context, mdClient *client.Client, environment directory := filepath.Join(baseDir, environment.Slug) for _, pack := range environment.Packages { - exportErr := pkg.ExportPackage(ctx, mdClient, &pack, directory) + exportErr := instance.ExportPackage(ctx, mdClient, &pack, directory) if exportErr != nil { return fmt.Errorf("failed to export package %s: %w", pack.Slug, exportErr) } diff --git a/internal/commands/image/docker_client.go b/internal/commands/image/docker_client.go index 0054e59c..63bca4a0 100644 --- a/internal/commands/image/docker_client.go +++ b/internal/commands/image/docker_client.go @@ -16,7 +16,7 @@ import ( "github.com/docker/docker/api/types/registry" dockerClient "github.com/docker/docker/client" "github.com/docker/docker/pkg/archive" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/prettylogs" ) diff --git a/internal/commands/image/push.go b/internal/commands/image/push.go index 16c04d1a..e35f98e3 100644 --- a/internal/commands/image/push.go +++ b/internal/commands/image/push.go @@ -7,7 +7,7 @@ import ( "os" "strings" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/prettylogs" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" diff --git a/internal/commands/image/push_test.go b/internal/commands/image/push_test.go index 8a22752c..999f78a0 100644 --- a/internal/commands/image/push_test.go +++ b/internal/commands/image/push_test.go @@ -7,7 +7,7 @@ import ( "io" "testing" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/commands/image" "github.com/Khan/genqlient/graphql" diff --git a/internal/commands/pkg/configure.go b/internal/commands/instance/configure.go similarity index 85% rename from internal/commands/pkg/configure.go rename to internal/commands/instance/configure.go index e23c76ec..7b44f08e 100644 --- a/internal/commands/pkg/configure.go +++ b/internal/commands/instance/configure.go @@ -1,12 +1,12 @@ -// Package pkg provides command implementations for managing Massdriver packages. -package pkg +// Package instance provides command implementations for managing Massdriver instances. +package instance import ( "context" "encoding/json" "os" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" ) diff --git a/internal/commands/pkg/configure_test.go b/internal/commands/instance/configure_test.go similarity index 83% rename from internal/commands/pkg/configure_test.go rename to internal/commands/instance/configure_test.go index 62b571ce..a5f06a02 100644 --- a/internal/commands/pkg/configure_test.go +++ b/internal/commands/instance/configure_test.go @@ -1,12 +1,12 @@ -package pkg_test +package instance_test import ( "net/http" "reflect" "testing" - "github.com/massdriver-cloud/mass/internal/api" - "github.com/massdriver-cloud/mass/internal/commands/pkg" + "github.com/massdriver-cloud/mass/internal/api/v0" + "github.com/massdriver-cloud/mass/internal/commands/instance" "github.com/massdriver-cloud/mass/internal/gqlmock" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" ) @@ -31,7 +31,7 @@ func TestRunConfigure(t *testing.T) { gqlmock.MustUnmarshalJSON(paramsJSON, ¶ms) return gqlmock.MockMutationResponse("configurePackage", map[string]any{ - "id": "pkg-id", + "id": "instance-id", "params": params, }) }, @@ -45,12 +45,12 @@ func TestRunConfigure(t *testing.T) { GQL: gqlmock.NewClientWithFuncResponseArray(responses), } - pkg, err := pkg.RunConfigure(t.Context(), &mdClient, "ecomm-prod-cache", params) + instance, err := instance.RunConfigure(t.Context(), &mdClient, "ecomm-prod-cache", params) if err != nil { t.Fatal(err) } - got := pkg.Params + got := instance.Params want := params if !reflect.DeepEqual(got, want) { @@ -78,7 +78,7 @@ func TestConfigurePackageInterpolation(t *testing.T) { gqlmock.MustUnmarshalJSON(paramsJSON, ¶ms) return gqlmock.MockMutationResponse("configurePackage", map[string]any{ - "id": "pkg-id", + "id": "instance-id", "params": params, }) }, @@ -90,12 +90,12 @@ func TestConfigurePackageInterpolation(t *testing.T) { params := map[string]any{"size": "${MEMORY_AMT}GB"} t.Setenv("MEMORY_AMT", "6") - pkg, err := pkg.RunConfigure(t.Context(), &mdClient, "ecomm-prod-cache", params) + instance, err := instance.RunConfigure(t.Context(), &mdClient, "ecomm-prod-cache", params) if err != nil { t.Fatal(err) } - got := pkg.Params + got := instance.Params want := map[string]any{ "size": "6GB", } diff --git a/internal/commands/pkg/deploy.go b/internal/commands/instance/deploy.go similarity index 81% rename from internal/commands/pkg/deploy.go rename to internal/commands/instance/deploy.go index 59c56658..07a11570 100644 --- a/internal/commands/pkg/deploy.go +++ b/internal/commands/instance/deploy.go @@ -1,4 +1,4 @@ -package pkg +package instance import ( "context" @@ -6,7 +6,7 @@ import ( "fmt" "time" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" ) @@ -16,14 +16,14 @@ var DeploymentStatusSleep = time.Duration(10) * time.Second // DeploymentTimeout is the maximum duration to wait for a deployment to complete. var DeploymentTimeout = time.Duration(5) * time.Minute -// RunDeploy deploys the named package and polls until the deployment completes or times out. +// RunDeploy deploys the named instance and polls until the deployment completes or times out. func RunDeploy(ctx context.Context, mdClient *client.Client, name, message string) (*api.Deployment, error) { - pkg, err := api.GetPackage(ctx, mdClient, name) + instance, err := api.GetPackage(ctx, mdClient, name) if err != nil { return nil, err } - deployment, err := api.DeployPackage(ctx, mdClient, pkg.Environment.ID, pkg.Manifest.ID, message) + deployment, err := api.DeployPackage(ctx, mdClient, instance.Environment.ID, instance.Manifest.ID, message) if err != nil { return deployment, err } diff --git a/internal/commands/pkg/deploy_test.go b/internal/commands/instance/deploy_test.go similarity index 74% rename from internal/commands/pkg/deploy_test.go rename to internal/commands/instance/deploy_test.go index 06b50829..2b3df9e6 100644 --- a/internal/commands/pkg/deploy_test.go +++ b/internal/commands/instance/deploy_test.go @@ -1,10 +1,10 @@ -package pkg_test +package instance_test import ( "testing" - "github.com/massdriver-cloud/mass/internal/api" - "github.com/massdriver-cloud/mass/internal/commands/pkg" + "github.com/massdriver-cloud/mass/internal/api/v0" + "github.com/massdriver-cloud/mass/internal/commands/instance" "github.com/massdriver-cloud/mass/internal/gqlmock" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" ) @@ -32,9 +32,9 @@ func TestRunDeploy(t *testing.T) { mdClient := client.Client{ GQL: gqlmock.NewClientWithJSONResponseArray(responses), } - pkg.DeploymentStatusSleep = 0 //nolint:reassign // intentionally overriding sleep duration in tests + instance.DeploymentStatusSleep = 0 //nolint:reassign // intentionally overriding sleep duration in tests - deployment, err := pkg.RunDeploy(t.Context(), &mdClient, "ecomm-prod-cache", "foo") + deployment, err := instance.RunDeploy(t.Context(), &mdClient, "ecomm-prod-cache", "foo") if err != nil { t.Fatal(err) } diff --git a/internal/commands/pkg/export.go b/internal/commands/instance/export.go similarity index 99% rename from internal/commands/pkg/export.go rename to internal/commands/instance/export.go index 6bb703d6..4e96e0ed 100644 --- a/internal/commands/pkg/export.go +++ b/internal/commands/instance/export.go @@ -1,4 +1,4 @@ -package pkg +package instance import ( "context" @@ -8,7 +8,7 @@ import ( "os" "path/filepath" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/bundle" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" diff --git a/internal/commands/pkg/export_test.go b/internal/commands/instance/export_test.go similarity index 91% rename from internal/commands/pkg/export_test.go rename to internal/commands/instance/export_test.go index dac76831..48eece52 100644 --- a/internal/commands/pkg/export_test.go +++ b/internal/commands/instance/export_test.go @@ -1,4 +1,4 @@ -package pkg_test +package instance_test import ( "context" @@ -6,8 +6,8 @@ import ( "testing" "time" - "github.com/massdriver-cloud/mass/internal/api" - "github.com/massdriver-cloud/mass/internal/commands/pkg" + "github.com/massdriver-cloud/mass/internal/api/v0" + "github.com/massdriver-cloud/mass/internal/commands/instance" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -71,7 +71,7 @@ func (msf *MockStateFetcher) FetchState(ctx context.Context, packageID string, s func TestExportPackage(t *testing.T) { tests := []struct { name string - pkg *api.Package + instance *api.Package baseDir string setupMocks func(*MockFileSystem, *MockBundleFetcher, *MockArtifactDownloader, *MockStateFetcher) expectedDirs []string @@ -80,8 +80,8 @@ func TestExportPackage(t *testing.T) { }{ { name: "export provisioned package with all components", - pkg: &api.Package{ - ID: "pkg-123", + instance: &api.Package{ + ID: "instance-123", Slug: "test-package-0001", Status: string(api.PackageStatusProvisioned), Params: map[string]any{ @@ -125,7 +125,7 @@ func TestExportPackage(t *testing.T) { mfs.On("WriteFile", "/tmp/export/test-manifest/artifact_output.json", mock.Anything, os.FileMode(0644)).Return(nil) // State fetcher expectations - msf.On("FetchState", mock.Anything, "pkg-123", "src").Return(map[string]any{"version": "1.0"}, nil) + msf.On("FetchState", mock.Anything, "instance-123", "src").Return(map[string]any{"version": "1.0"}, nil) mfs.On("WriteFile", "/tmp/export/test-manifest/src.tfstate.json", mock.Anything, os.FileMode(0644)).Return(nil) }, expectedDirs: []string{"/tmp/export/test-manifest"}, @@ -137,8 +137,8 @@ func TestExportPackage(t *testing.T) { }, { name: "skip bundle export if not OCI compliant and skip state if nil", - pkg: &api.Package{ - ID: "pkg-123", + instance: &api.Package{ + ID: "instance-123", Slug: "test-package-0001", Status: string(api.PackageStatusProvisioned), Params: map[string]any{ @@ -182,7 +182,7 @@ func TestExportPackage(t *testing.T) { mfs.On("WriteFile", "/tmp/export/test-manifest/artifact_output.json", mock.Anything, os.FileMode(0644)).Return(nil) // State fetcher expectations - msf.On("FetchState", mock.Anything, "pkg-123", "src").Return(nil, pkg.ErrNoState) + msf.On("FetchState", mock.Anything, "instance-123", "src").Return(nil, instance.ErrNoState) }, expectedDirs: []string{"/tmp/export/test-manifest"}, expectedFiles: []string{ @@ -193,8 +193,8 @@ func TestExportPackage(t *testing.T) { }, { name: "export external package with remote references only", - pkg: &api.Package{ - ID: "pkg-456", + instance: &api.Package{ + ID: "instance-456", Slug: "external-package-0001", Status: string(api.PackageStatusExternal), RemoteReferences: []api.RemoteReference{ @@ -226,8 +226,8 @@ func TestExportPackage(t *testing.T) { }, { name: "skip export for non-provisioned non-external package", - pkg: &api.Package{ - ID: "pkg-789", + instance: &api.Package{ + ID: "instance-789", Slug: "pending-package-0001", Status: "PENDING", Bundle: &api.Bundle{ @@ -247,8 +247,8 @@ func TestExportPackage(t *testing.T) { }, { name: "validation error for invalid package", - pkg: &api.Package{ - ID: "invalid-pkg", + instance: &api.Package{ + ID: "invalid-instance", // Missing required fields }, baseDir: "/tmp/export", @@ -272,7 +272,7 @@ func TestExportPackage(t *testing.T) { tt.setupMocks(mockFS, mockBundleFetcher, mockArtifactDownloader, mockStateFetcher) // Create config with mocks - config := pkg.ExportPackageConfig{ + config := instance.ExportPackageConfig{ FileSystem: mockFS, BundleFetcher: mockBundleFetcher, ArtifactDownloader: mockArtifactDownloader, @@ -283,7 +283,7 @@ func TestExportPackage(t *testing.T) { ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() - err := pkg.ExportPackageWithConfig(ctx, &config, tt.pkg, tt.baseDir) + err := instance.ExportPackageWithConfig(ctx, &config, tt.instance, tt.baseDir) // Check error expectation if tt.wantErr { @@ -312,7 +312,7 @@ func TestExportPackage(t *testing.T) { func TestExportPackage_FileSystemError(t *testing.T) { pack := &api.Package{ - ID: "pkg-123", + ID: "instance-123", Slug: "test-package-0001", Status: string(api.PackageStatusProvisioned), Bundle: &api.Bundle{ @@ -327,7 +327,7 @@ func TestExportPackage_FileSystemError(t *testing.T) { mockFS := &MockFileSystem{} mockFS.On("MkdirAll", mock.Anything, mock.Anything).Return(os.ErrPermission) - config := pkg.ExportPackageConfig{ + config := instance.ExportPackageConfig{ FileSystem: mockFS, BundleFetcher: &MockBundleFetcher{}, ArtifactDownloader: &MockArtifactDownloader{}, @@ -335,7 +335,7 @@ func TestExportPackage_FileSystemError(t *testing.T) { } ctx := t.Context() - err := pkg.ExportPackageWithConfig(ctx, &config, pack, "/tmp/export") + err := instance.ExportPackageWithConfig(ctx, &config, pack, "/tmp/export") require.Error(t, err) assert.Contains(t, err.Error(), "failed to create directory") diff --git a/internal/commands/pkg/patch.go b/internal/commands/instance/patch.go similarity index 93% rename from internal/commands/pkg/patch.go rename to internal/commands/instance/patch.go index 92ca6902..d76741c6 100644 --- a/internal/commands/pkg/patch.go +++ b/internal/commands/instance/patch.go @@ -1,11 +1,11 @@ -package pkg +package instance import ( "context" "errors" "github.com/itchyny/gojq" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" ) diff --git a/internal/commands/pkg/patch_test.go b/internal/commands/instance/patch_test.go similarity index 82% rename from internal/commands/pkg/patch_test.go rename to internal/commands/instance/patch_test.go index 2689f902..1dc0d238 100644 --- a/internal/commands/pkg/patch_test.go +++ b/internal/commands/instance/patch_test.go @@ -1,12 +1,12 @@ -package pkg_test +package instance_test import ( "net/http" "reflect" "testing" - "github.com/massdriver-cloud/mass/internal/api" - "github.com/massdriver-cloud/mass/internal/commands/pkg" + "github.com/massdriver-cloud/mass/internal/api/v0" + "github.com/massdriver-cloud/mass/internal/commands/instance" "github.com/massdriver-cloud/mass/internal/gqlmock" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" ) @@ -35,7 +35,7 @@ func TestRunPatch(t *testing.T) { gqlmock.MustUnmarshalJSON(paramsJSON, ¶ms) return gqlmock.MockMutationResponse("configurePackage", map[string]any{ - "id": "pkg-id", + "id": "instance-id", "params": params, }) }, @@ -46,12 +46,12 @@ func TestRunPatch(t *testing.T) { } setValues := []string{".cidr = \"10.0.0.0/20\""} - pkg, err := pkg.RunPatch(t.Context(), &mdClient, "ecomm-prod-cache", setValues) + instance, err := instance.RunPatch(t.Context(), &mdClient, "ecomm-prod-cache", setValues) if err != nil { t.Fatal(err) } - got := pkg.Params + got := instance.Params want := map[string]any{ "cidr": "10.0.0.0/20", } diff --git a/internal/commands/pkg/reset.go b/internal/commands/instance/reset.go similarity index 84% rename from internal/commands/pkg/reset.go rename to internal/commands/instance/reset.go index ae7f5701..0e8c72a3 100644 --- a/internal/commands/pkg/reset.go +++ b/internal/commands/instance/reset.go @@ -1,9 +1,9 @@ -package pkg +package instance import ( "context" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" ) diff --git a/internal/commands/pkg/reset_test.go b/internal/commands/instance/reset_test.go similarity index 57% rename from internal/commands/pkg/reset_test.go rename to internal/commands/instance/reset_test.go index 55a091a7..1b026768 100644 --- a/internal/commands/pkg/reset_test.go +++ b/internal/commands/instance/reset_test.go @@ -1,11 +1,11 @@ -package pkg_test +package instance_test import ( "net/http" "testing" - "github.com/massdriver-cloud/mass/internal/api" - "github.com/massdriver-cloud/mass/internal/commands/pkg" + "github.com/massdriver-cloud/mass/internal/api/v0" + "github.com/massdriver-cloud/mass/internal/commands/instance" "github.com/massdriver-cloud/mass/internal/gqlmock" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" ) @@ -14,7 +14,7 @@ func TestRunReset(t *testing.T) { responses := []gqlmock.ResponseFunc{ func(req *http.Request) any { return gqlmock.MockQueryResponse("getPackage", api.Package{ - ID: "pkg-uuid1", + ID: "instance-uuid1", Slug: "ecomm-prod-cache", Manifest: &api.Manifest{ID: "manifest-id"}, Environment: &api.Environment{ID: "target-id"}, @@ -22,7 +22,7 @@ func TestRunReset(t *testing.T) { }, func(req *http.Request) any { return gqlmock.MockMutationResponse("resetPackage", map[string]any{ - "id": "pkg-uuid1", + "id": "instance-uuid1", "slug": "ecomm-prod-cache", "status": "ready", }) @@ -33,18 +33,18 @@ func TestRunReset(t *testing.T) { GQL: gqlmock.NewClientWithFuncResponseArray(responses), } - pkg, err := pkg.RunReset(t.Context(), &mdClient, "ecomm-prod-cache") + instance, err := instance.RunReset(t.Context(), &mdClient, "ecomm-prod-cache") if err != nil { t.Fatal(err) } - if pkg.ID != "pkg-uuid1" { - t.Errorf("got %v, wanted %v", pkg.ID, "pkg-uuid1") + if instance.ID != "instance-uuid1" { + t.Errorf("got %v, wanted %v", instance.ID, "instance-uuid1") } - if pkg.Slug != "ecomm-prod-cache" { - t.Errorf("got %v, wanted %v", pkg.Slug, "ecomm-prod-cache") + if instance.Slug != "ecomm-prod-cache" { + t.Errorf("got %v, wanted %v", instance.Slug, "ecomm-prod-cache") } - if pkg.Status != "ready" { - t.Errorf("got %v, wanted %v", pkg.Status, "ready") + if instance.Status != "ready" { + t.Errorf("got %v, wanted %v", instance.Status, "ready") } } diff --git a/internal/commands/preview/decommission.go b/internal/commands/preview/decommission.go index 1336bd50..52647ecc 100644 --- a/internal/commands/preview/decommission.go +++ b/internal/commands/preview/decommission.go @@ -4,7 +4,7 @@ package preview import ( "context" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" ) diff --git a/internal/commands/preview/deploy.go b/internal/commands/preview/deploy.go index 86d292e2..c41722e7 100644 --- a/internal/commands/preview/deploy.go +++ b/internal/commands/preview/deploy.go @@ -5,7 +5,7 @@ import ( "encoding/json" "os" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" ) diff --git a/internal/commands/preview/deploy_test.go b/internal/commands/preview/deploy_test.go index 33482a1a..b260aef9 100644 --- a/internal/commands/preview/deploy_test.go +++ b/internal/commands/preview/deploy_test.go @@ -6,7 +6,7 @@ import ( "reflect" "testing" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/commands/preview" "github.com/massdriver-cloud/mass/internal/gqlmock" diff --git a/internal/commands/preview/model.go b/internal/commands/preview/model.go index 09f75e49..8024fd5d 100644 --- a/internal/commands/preview/model.go +++ b/internal/commands/preview/model.go @@ -6,7 +6,7 @@ import ( "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/tui/components/artdeftable" "github.com/massdriver-cloud/mass/internal/tui/components/artifacttable" ) diff --git a/internal/commands/preview/new.go b/internal/commands/preview/new.go index d2723e43..058cbc0c 100644 --- a/internal/commands/preview/new.go +++ b/internal/commands/preview/new.go @@ -3,7 +3,7 @@ package preview import ( "context" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/debuglog" "github.com/massdriver-cloud/mass/internal/tui/components/artdeftable" diff --git a/internal/commands/preview/new_test.go b/internal/commands/preview/new_test.go index c46d65b3..52dfc10b 100644 --- a/internal/commands/preview/new_test.go +++ b/internal/commands/preview/new_test.go @@ -4,7 +4,7 @@ import ( "reflect" "testing" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/commands/preview" "github.com/massdriver-cloud/mass/internal/gqlmock" "github.com/massdriver-cloud/mass/internal/tui/teahelper" diff --git a/internal/commands/project/export.go b/internal/commands/project/export.go index 15ef72ee..7c7e4ed2 100644 --- a/internal/commands/project/export.go +++ b/internal/commands/project/export.go @@ -6,7 +6,7 @@ import ( "fmt" "path/filepath" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/commands/environment" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" diff --git a/internal/definition/delete.go b/internal/definition/delete.go index ab3444f0..4d3e86d6 100644 --- a/internal/definition/delete.go +++ b/internal/definition/delete.go @@ -7,7 +7,7 @@ import ( "os" "strings" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/prettylogs" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" diff --git a/internal/definition/get.go b/internal/definition/get.go index fb7c5d6f..4c6d1e43 100644 --- a/internal/definition/get.go +++ b/internal/definition/get.go @@ -4,7 +4,7 @@ import ( "context" "encoding/json" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" ) diff --git a/internal/definition/get_test.go b/internal/definition/get_test.go index 221c594d..18f63eb2 100644 --- a/internal/definition/get_test.go +++ b/internal/definition/get_test.go @@ -4,7 +4,7 @@ import ( "reflect" "testing" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/definition" "github.com/massdriver-cloud/mass/internal/gqlmock" diff --git a/internal/definition/publish.go b/internal/definition/publish.go index ff173257..dcaf7877 100644 --- a/internal/definition/publish.go +++ b/internal/definition/publish.go @@ -5,7 +5,7 @@ import ( "fmt" "net/url" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/jsonschema" "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" ) diff --git a/internal/definition/publish_test.go b/internal/definition/publish_test.go index f1aee242..1cbcb93c 100644 --- a/internal/definition/publish_test.go +++ b/internal/definition/publish_test.go @@ -6,7 +6,7 @@ import ( "os" "testing" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/definition" "github.com/massdriver-cloud/mass/internal/gqlmock" diff --git a/internal/tui/components/artdeftable/model.go b/internal/tui/components/artdeftable/model.go index 59c2758f..5bfe70c6 100644 --- a/internal/tui/components/artdeftable/model.go +++ b/internal/tui/components/artdeftable/model.go @@ -8,7 +8,7 @@ import ( "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" "github.com/evertras/bubble-table/table" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "golang.org/x/text/cases" "golang.org/x/text/language" ) diff --git a/internal/tui/components/artdeftable/model_test.go b/internal/tui/components/artdeftable/model_test.go index 261c050e..ddce36f3 100644 --- a/internal/tui/components/artdeftable/model_test.go +++ b/internal/tui/components/artdeftable/model_test.go @@ -4,7 +4,7 @@ import ( "testing" tea "github.com/charmbracelet/bubbletea" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/tui/components/artdeftable" "github.com/massdriver-cloud/mass/internal/tui/teahelper" ) diff --git a/internal/tui/components/artifacttable/model.go b/internal/tui/components/artifacttable/model.go index fe104ef7..81bb8b5b 100644 --- a/internal/tui/components/artifacttable/model.go +++ b/internal/tui/components/artifacttable/model.go @@ -7,7 +7,7 @@ import ( "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" "github.com/evertras/bubble-table/table" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" ) // Model is the Bubble Tea model for the artifact selection table. diff --git a/internal/tui/components/artifacttable/model_test.go b/internal/tui/components/artifacttable/model_test.go index cdb9ee5d..3092b35a 100644 --- a/internal/tui/components/artifacttable/model_test.go +++ b/internal/tui/components/artifacttable/model_test.go @@ -4,7 +4,7 @@ import ( "testing" tea "github.com/charmbracelet/bubbletea" - "github.com/massdriver-cloud/mass/internal/api" + "github.com/massdriver-cloud/mass/internal/api/v0" "github.com/massdriver-cloud/mass/internal/tui/components/artifacttable" "github.com/massdriver-cloud/mass/internal/tui/teahelper" )