From 4424ab8ab5af8533f75936e7d2aec41e025f4286 Mon Sep 17 00:00:00 2001 From: chrisghill Date: Wed, 7 Jan 2026 13:38:45 -0700 Subject: [PATCH 1/2] add mass package reset command --- cmd/package.go | 34 ++++++ docs/generated/mass_artifact.md | 6 -- docs/generated/mass_credential.md | 4 - docs/generated/mass_definition.md | 7 -- docs/generated/mass_environment.md | 7 -- docs/generated/mass_logs.md | 61 +++++++++++ docs/generated/mass_package.md | 9 +- docs/generated/mass_package_reset.md | 47 ++++++++ docs/generated/mass_project.md | 6 -- docs/helpdocs/artifact.md | 6 -- docs/helpdocs/credential.md | 4 - docs/helpdocs/definition.md | 7 -- docs/helpdocs/environment.md | 7 -- docs/helpdocs/package.md | 8 -- docs/helpdocs/package/reset.md | 15 +++ docs/helpdocs/project.md | 6 -- pkg/api/genqlient.graphql | 99 +++++++++-------- pkg/api/package.go | 18 ++++ pkg/api/package_test.go | 33 ++++++ pkg/api/schema.graphql | 66 ++++++++++++ pkg/api/zz_generated.go | 156 +++++++++++++++++++++++++++ pkg/commands/pkg/reset.go | 19 ++++ pkg/commands/pkg/reset_test.go | 50 +++++++++ 23 files changed, 557 insertions(+), 118 deletions(-) create mode 100644 docs/generated/mass_logs.md create mode 100644 docs/generated/mass_package_reset.md create mode 100644 docs/helpdocs/package/reset.md create mode 100644 pkg/commands/pkg/reset.go create mode 100644 pkg/commands/pkg/reset_test.go diff --git a/cmd/package.go b/cmd/package.go index ed2e95ae..15a3f51d 100644 --- a/cmd/package.go +++ b/cmd/package.go @@ -122,6 +122,15 @@ func NewCmdPkg() *cobra.Command { } 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, + } + pkgCmd.AddCommand(pkgConfigureCmd) pkgCmd.AddCommand(pkgDeployCmd) pkgCmd.AddCommand(pkgExportCmd) @@ -130,6 +139,7 @@ func NewCmdPkg() *cobra.Command { pkgCmd.AddCommand(pkgCreateCmd) pkgCmd.AddCommand(pkgVersionCmd) pkgCmd.AddCommand(pkgDestroyCmd) + pkgCmd.AddCommand(pkgResetCmd) return pkgCmd } @@ -456,3 +466,27 @@ func runPkgDestroy(cmd *cobra.Command, args []string) error { return nil } + +func runPkgReset(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) + } + + 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 +} diff --git a/docs/generated/mass_artifact.md b/docs/generated/mass_artifact.md index c53e53d3..de5299f5 100644 --- a/docs/generated/mass_artifact.md +++ b/docs/generated/mass_artifact.md @@ -14,12 +14,6 @@ Manage artifacts Artifacts represent infrastructure resources and connections in Massdriver. They can be provisioned by bundles or manually imported. -## Commands - -- `get`: Retrieve an artifact's details and metadata -- `download`: Download an artifact in the specified format -- `import`: Import a custom artifact - ### Options diff --git a/docs/generated/mass_credential.md b/docs/generated/mass_credential.md index d906b03e..6eaeef44 100644 --- a/docs/generated/mass_credential.md +++ b/docs/generated/mass_credential.md @@ -14,10 +14,6 @@ Credential management Manage credential artifacts in your organization. -## Commands - -- `list`: List all credential artifacts - ### Options diff --git a/docs/generated/mass_definition.md b/docs/generated/mass_definition.md index 340e7a92..5b524162 100644 --- a/docs/generated/mass_definition.md +++ b/docs/generated/mass_definition.md @@ -20,13 +20,6 @@ Artifact definitions are used to: - Control how artifacts are displayed in the UI - Define connections between artifacts -## Commands - -- `get`: Retrieve an artifact definition's schema and metadata -- `list`: List all available artifact definitions -- `publish`: Publish a new or updated artifact definition -- `delete`: Delete an artifact definition (requires administrator permissions) - ### Options diff --git a/docs/generated/mass_environment.md b/docs/generated/mass_environment.md index 2f63a871..5e75e552 100644 --- a/docs/generated/mass_environment.md +++ b/docs/generated/mass_environment.md @@ -16,13 +16,6 @@ Environment management Environments can be modeled by application stage (production, staging, development), by region (prod-usw, prod-eu), and even ephemerally per developer (alice-dev, bob-dev). -## Commands - -- `export`: Export an environment to local filesystem -- `get`: Retrieve environment details and configuration -- `list`: List all environments in a project -- `default`: Set an artifact as the default connection for an environment - ### Options diff --git a/docs/generated/mass_logs.md b/docs/generated/mass_logs.md new file mode 100644 index 00000000..aa42db49 --- /dev/null +++ b/docs/generated/mass_logs.md @@ -0,0 +1,61 @@ +--- +id: mass_logs.md +slug: /cli/commands/mass_logs +title: Mass Logs +sidebar_label: Mass Logs +--- +## mass logs + +Get deployment logs + +### Synopsis + +# Get Deployment Logs + +Retrieves and outputs the log stream for a specific deployment. The logs are dumped to stdout in their original format. + +## Usage + +```bash +mass logs +``` + +Where `` is the UUID of the deployment. + +## Examples + +```bash +# Get logs for a deployment +mass logs 12345678-1234-1234-1234-123456789012 + +# Pipe logs to a file +mass logs 12345678-1234-1234-1234-123456789012 > deployment.log +``` + +## Notes + +- Logs are output to stdout in their original format +- This command does not support tailing/following logs - it dumps all available logs +- The deployment ID can be found in the Massdriver UI or from deployment-related commands + + +``` +mass logs [deployment-id] [flags] +``` + +### Examples + +``` + # Get logs for a deployment + mass logs 12345678-1234-1234-1234-123456789012 +``` + +### Options + +``` + -h, --help help for logs +``` + +### SEE ALSO + +* [mass](/cli/commands/mass) - Massdriver Cloud CLI diff --git a/docs/generated/mass_package.md b/docs/generated/mass_package.md index 61ca1dbd..19c616bd 100644 --- a/docs/generated/mass_package.md +++ b/docs/generated/mass_package.md @@ -20,14 +20,6 @@ Packages are used to: - Manage environment-specific settings - Connect different components together -## Commands - -- `configure`: Update package configuration -- `deploy`: Deploy a package to an environment -- `export`: Export a package to your local filesystem -- `get`: Retrieve package details and configuration -- `patch`: Update individual package parameter values - ### Options @@ -45,4 +37,5 @@ Packages are used to: * [mass package export](/cli/commands/mass_package_export) - Export packages * [mass package get](/cli/commands/mass_package_get) - Get a package * [mass package patch](/cli/commands/mass_package_patch) - Patch individual package parameter values +* [mass package reset](/cli/commands/mass_package_reset) - Reset package status to 'Initialized' * [mass package version](/cli/commands/mass_package_version) - Set package version diff --git a/docs/generated/mass_package_reset.md b/docs/generated/mass_package_reset.md new file mode 100644 index 00000000..c83860e2 --- /dev/null +++ b/docs/generated/mass_package_reset.md @@ -0,0 +1,47 @@ +--- +id: mass_package_reset.md +slug: /cli/commands/mass_package_reset +title: Mass Package Reset +sidebar_label: Mass Package Reset +--- +## mass package reset + +Reset package 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 package reset -- [flags] +``` + +### Examples + +``` +mass package reset api-prod-db +``` + +### Options + +``` + -h, --help help for reset +``` + +### SEE ALSO + +* [mass package](/cli/commands/mass_package) - Manage packages of IaC deployed in environments. diff --git a/docs/generated/mass_project.md b/docs/generated/mass_project.md index 0cd6ce62..49253d85 100644 --- a/docs/generated/mass_project.md +++ b/docs/generated/mass_project.md @@ -16,12 +16,6 @@ Project management A project can encompass many environments (permanent or ephemeral) and manages the parity across those environments. -## Commands - -- `export`: Export a project to your local filesystem -- `get`: Retrieve project details and configuration -- `list`: List all projects in your organization - ### Options diff --git a/docs/helpdocs/artifact.md b/docs/helpdocs/artifact.md index 7eee4f2d..e7b92b80 100644 --- a/docs/helpdocs/artifact.md +++ b/docs/helpdocs/artifact.md @@ -1,9 +1,3 @@ # Manage Massdriver artifacts Artifacts represent infrastructure resources and connections in Massdriver. They can be provisioned by bundles or manually imported. - -## Commands - -- `get`: Retrieve an artifact's details and metadata -- `download`: Download an artifact in the specified format -- `import`: Import a custom artifact diff --git a/docs/helpdocs/credential.md b/docs/helpdocs/credential.md index f5bcb100..7f1bfbd9 100644 --- a/docs/helpdocs/credential.md +++ b/docs/helpdocs/credential.md @@ -1,7 +1,3 @@ # Credential Management Manage credential artifacts in your organization. - -## Commands - -- `list`: List all credential artifacts diff --git a/docs/helpdocs/definition.md b/docs/helpdocs/definition.md index e5829e8c..c89f1429 100644 --- a/docs/helpdocs/definition.md +++ b/docs/helpdocs/definition.md @@ -7,10 +7,3 @@ Artifact definitions are used to: - Specify how artifacts are created and managed - Control how artifacts are displayed in the UI - Define connections between artifacts - -## Commands - -- `get`: Retrieve an artifact definition's schema and metadata -- `list`: List all available artifact definitions -- `publish`: Publish a new or updated artifact definition -- `delete`: Delete an artifact definition (requires administrator permissions) diff --git a/docs/helpdocs/environment.md b/docs/helpdocs/environment.md index 37094e02..660c21ba 100644 --- a/docs/helpdocs/environment.md +++ b/docs/helpdocs/environment.md @@ -3,10 +3,3 @@ [Environments](https://docs.massdriver.cloud/concepts/environments) are the workspaces that bundles will be deployed to. Environments can be modeled by application stage (production, staging, development), by region (prod-usw, prod-eu), and even ephemerally per developer (alice-dev, bob-dev). - -## Commands - -- `export`: Export an environment to local filesystem -- `get`: Retrieve environment details and configuration -- `list`: List all environments in a project -- `default`: Set an artifact as the default connection for an environment diff --git a/docs/helpdocs/package.md b/docs/helpdocs/package.md index 4fc5ad77..4090a2db 100644 --- a/docs/helpdocs/package.md +++ b/docs/helpdocs/package.md @@ -7,11 +7,3 @@ Packages are used to: - Configure application services - Manage environment-specific settings - Connect different components together - -## Commands - -- `configure`: Update package configuration -- `deploy`: Deploy a package to an environment -- `export`: Export a package to your local filesystem -- `get`: Retrieve package details and configuration -- `patch`: Update individual package parameter values diff --git a/docs/helpdocs/package/reset.md b/docs/helpdocs/package/reset.md new file mode 100644 index 00000000..9f6618f5 --- /dev/null +++ b/docs/helpdocs/package/reset.md @@ -0,0 +1,15 @@ +# 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 +``` diff --git a/docs/helpdocs/project.md b/docs/helpdocs/project.md index 77f78e90..606a5526 100644 --- a/docs/helpdocs/project.md +++ b/docs/helpdocs/project.md @@ -3,9 +3,3 @@ [Projects](https://docs.massdriver.cloud/concepts/projects) act as permission and replication boundaries in Massdriver. A project can encompass many environments (permanent or ephemeral) and manages the parity across those environments. - -## Commands - -- `export`: Export a project to your local filesystem -- `get`: Retrieve project details and configuration -- `list`: List all projects in your organization diff --git a/pkg/api/genqlient.graphql b/pkg/api/genqlient.graphql index ee8db667..2384db4f 100644 --- a/pkg/api/genqlient.graphql +++ b/pkg/api/genqlient.graphql @@ -155,6 +155,29 @@ mutation deleteArtifactDefinition($organizationId: ID!, $name: String!) { } +# BUNDLES + +query getBundle($organizationId: ID!, $id: ID!, $version: VersionConstraint) { + bundle(organizationId: $organizationId, id: $id, version: $version) { + id + name + version + description + spec + specVersion + icon + sourceUrl + paramsSchema + connectionsSchema + artifactsSchema + uiSchema + operatorGuide + createdAt + updatedAt + } +} + + # CONTAINER REPOSITORIES query containerRepository($orgId: ID!, $artifactId: ID!, $input: ContainerRepositoryInput!) { @@ -340,6 +363,25 @@ mutation createEnvironment($organizationId: ID!, $projectId: ID!, $name: String! } +# MANIFESTS + +mutation createManifest($organizationId: ID!, $bundleId: ID!, $projectId: ID!, $name: String!, $slug: String!, $description: String) { + createManifest(organizationId: $organizationId, bundleId: $bundleId, projectId: $projectId, name: $name, slug: $slug, description: $description) { + result { + id + name + slug + description + } + successful + # @genqlient(typename: "MutationValidationError") + messages { + message + } + } +} + + # OCI query getOciRepo($organizationId: ID!, $id: ID!) { @@ -455,6 +497,21 @@ mutation decommissionPackage($organizationId: ID!, $id: ID!, $message: String) { } } +mutation resetPackage($organizationId: ID!, $id: ID!, $deleteState: Boolean!, $deleteParams: Boolean!, $deleteDeployments: Boolean!) { + resetPackage(organizationId: $organizationId, id: $id, deleteState: $deleteState, deleteParams: $deleteParams, deleteDeployments: $deleteDeployments) { + successful + result { + id + slug + status + } + # @genqlient(typename: "MutationValidationError") + messages { + message + } + } +} + # PROJECTS @@ -571,45 +628,3 @@ query getServer() { version } } - - -# BUNDLES - -query getBundle($organizationId: ID!, $id: ID!, $version: VersionConstraint) { - bundle(organizationId: $organizationId, id: $id, version: $version) { - id - name - version - description - spec - specVersion - icon - sourceUrl - paramsSchema - connectionsSchema - artifactsSchema - uiSchema - operatorGuide - createdAt - updatedAt - } -} - - -# MANIFESTS - -mutation createManifest($organizationId: ID!, $bundleId: ID!, $projectId: ID!, $name: String!, $slug: String!, $description: String) { - createManifest(organizationId: $organizationId, bundleId: $bundleId, projectId: $projectId, name: $name, slug: $slug, description: $description) { - result { - id - name - slug - description - } - successful - # @genqlient(typename: "MutationValidationError") - messages { - message - } - } -} diff --git a/pkg/api/package.go b/pkg/api/package.go index dd2d6043..3d0449c0 100644 --- a/pkg/api/package.go +++ b/pkg/api/package.go @@ -87,3 +87,21 @@ func DecommissionPackage(ctx context.Context, mdClient *client.Client, id string return nil, NewMutationError("failed to decommission package", response.DecommissionPackage.Messages) } + +func ResetPackage(ctx context.Context, mdClient *client.Client, id string) (*Package, error) { + deleteState := false + deleteParams := false + deleteDeployments := true + + response, err := resetPackage(ctx, mdClient.GQL, mdClient.Config.OrganizationID, id, deleteState, deleteParams, deleteDeployments) + + if err != nil { + return nil, err + } + + if response.ResetPackage.Successful { + return toPackage(response.ResetPackage.Result) + } + + return nil, NewMutationError("failed to reset package", response.ResetPackage.Messages) +} diff --git a/pkg/api/package_test.go b/pkg/api/package_test.go index 70468fa6..938095be 100644 --- a/pkg/api/package_test.go +++ b/pkg/api/package_test.go @@ -89,3 +89,36 @@ func TestConfigurePackage(t *testing.T) { t.Errorf("got %v, wanted %v", got, want) } } + +func TestResetPackage(t *testing.T) { + gqlClient := gqlmock.NewClientWithSingleJSONResponse(map[string]any{ + "data": map[string]any{ + "resetPackage": map[string]any{ + "result": map[string]any{ + "id": "pkg-uuid1", + "slug": "ecomm-prod-cache", + "status": "ready", + }, + "successful": true, + }, + }, + }) + mdClient := client.Client{ + GQL: gqlClient, + } + + pkg, err := api.ResetPackage(t.Context(), &mdClient, "pkg-uuid1") + if err != nil { + t.Fatal(err) + } + + if pkg.ID != "pkg-uuid1" { + t.Errorf("got %v, wanted %v", pkg.ID, "pkg-uuid1") + } + if pkg.Slug != "ecomm-prod-cache" { + t.Errorf("got %v, wanted %v", pkg.Slug, "ecomm-prod-cache") + } + if pkg.Status != "ready" { + t.Errorf("got %v, wanted %v", pkg.Status, "ready") + } +} diff --git a/pkg/api/schema.graphql b/pkg/api/schema.graphql index be543faf..aec9e414 100644 --- a/pkg/api/schema.graphql +++ b/pkg/api/schema.graphql @@ -213,6 +213,12 @@ type RootQueryType { nodeContexts(organizationId: ID!, environmentId: ID!): [NodeContext] + "List all available integration types" + integrationTypes: [IntegrationType] + + "List all integrations enabled for an organization" + integrations(organizationId: ID!): [Integration] + environment( organizationId: ID! @@ -468,6 +474,12 @@ type RootMutationType { unlinkManifests(organizationId: ID!, linkId: ID!): LinkPayload + "Enable an integration for an organization" + enableIntegration(organizationId: ID!, integrationTypeId: String!, config: JSON!, auth: JSON!): IntegrationPayload + + "Disable an integration for an organization" + disableIntegration(organizationId: ID!, integrationTypeId: String!): Boolean + "Create an environment" createEnvironment(organizationId: ID!, projectId: ID!, name: String!, slug: String!, description: String): EnvironmentPayload @@ -805,18 +817,41 @@ enum ServerMode { MANAGED } +enum EmailAuthMethodType { + PASSWORDLESS + PASSKEY +} + type SsoProvider { name: String! "OAuth Login Flow URL" loginUrl: String! + + "Optional icon URL for UI display" + uiIconUrl: String + + "Optional label for UI display" + uiLabel: String +} + +type EmailAuthMethod { + "Type of email-based authentication method" + name: EmailAuthMethodType! } type Server { appUrl: String! + version: String! + mode: ServerMode! + ssoProviders: [SsoProvider] + + "List of enabled email-based authentication methods" + emailAuthMethods: [EmailAuthMethod] + features: ServerFeatures! @deprecated(reason: "") } @@ -1163,6 +1198,9 @@ type Package { paramsSchema: JSON! + "The operator guide for the bundle with params rendered via templating if enabled" + operatorGuide: Markdown + "The environment this package will be deployed to" environment: Environment! @@ -1652,6 +1690,34 @@ type EnvironmentPayload { result: Environment } +type IntegrationType { + name: String! + id: String! + configSchema: JSON! + authSchema: JSON! +} + +type Integration { + id: ID! + organizationId: ID! + integrationType: String! + config: JSON! + auth: JSON! + createdAt: DateTime! + updatedAt: DateTime! +} + +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 +} + "A connection between two nodes in the diagram" type Link { "Unique identifier for the link" diff --git a/pkg/api/zz_generated.go b/pkg/api/zz_generated.go index 8f1e2ca2..0f0615c1 100644 --- a/pkg/api/zz_generated.go +++ b/pkg/api/zz_generated.go @@ -922,6 +922,30 @@ func (v *__publishArtifactDefinitionInput) __premarshalJSON() (*__premarshal__pu return &retval, nil } +// __resetPackageInput is used internally by genqlient +type __resetPackageInput struct { + OrganizationId string `json:"organizationId"` + Id string `json:"id"` + DeleteState bool `json:"deleteState"` + DeleteParams bool `json:"deleteParams"` + DeleteDeployments bool `json:"deleteDeployments"` +} + +// GetOrganizationId returns __resetPackageInput.OrganizationId, and is useful for accessing the field via an interface. +func (v *__resetPackageInput) GetOrganizationId() string { return v.OrganizationId } + +// GetId returns __resetPackageInput.Id, and is useful for accessing the field via an interface. +func (v *__resetPackageInput) GetId() string { return v.Id } + +// GetDeleteState returns __resetPackageInput.DeleteState, and is useful for accessing the field via an interface. +func (v *__resetPackageInput) GetDeleteState() bool { return v.DeleteState } + +// GetDeleteParams returns __resetPackageInput.DeleteParams, and is useful for accessing the field via an interface. +func (v *__resetPackageInput) GetDeleteParams() bool { return v.DeleteParams } + +// GetDeleteDeployments returns __resetPackageInput.DeleteDeployments, and is useful for accessing the field via an interface. +func (v *__resetPackageInput) GetDeleteDeployments() bool { return v.DeleteDeployments } + // __setPackageVersionInput is used internally by genqlient type __setPackageVersionInput struct { OrganizationId string `json:"organizationId"` @@ -4630,6 +4654,87 @@ func (v *publishArtifactDefinitionResponse) GetPublishArtifactDefinition() publi return v.PublishArtifactDefinition } +// resetPackageResetPackagePackagePayload includes the requested fields of the GraphQL type PackagePayload. +type resetPackageResetPackagePackagePayload struct { + // Indicates if the mutation completed successfully or not. + Successful bool `json:"successful"` + // The object created/updated/deleted by the mutation. May be null if mutation failed. + Result resetPackageResetPackagePackagePayloadResultPackage `json:"result"` + // A list of failed validations. May be blank or null if mutation succeeded. + Messages []MutationValidationError `json:"messages"` +} + +// GetSuccessful returns resetPackageResetPackagePackagePayload.Successful, and is useful for accessing the field via an interface. +func (v *resetPackageResetPackagePackagePayload) GetSuccessful() bool { return v.Successful } + +// GetResult returns resetPackageResetPackagePackagePayload.Result, and is useful for accessing the field via an interface. +func (v *resetPackageResetPackagePackagePayload) GetResult() resetPackageResetPackagePackagePayloadResultPackage { + return v.Result +} + +// GetMessages returns resetPackageResetPackagePackagePayload.Messages, and is useful for accessing the field via an interface. +func (v *resetPackageResetPackagePackagePayload) GetMessages() []MutationValidationError { + return v.Messages +} + +// resetPackageResetPackagePackagePayloadResultPackage includes the requested fields of the GraphQL type Package. +// The GraphQL type's documentation follows. +// +// A deployed instance of a bundle in the context of its manifest +type resetPackageResetPackagePackagePayloadResultPackage struct { + // Unique identifier + Id string `json:"id"` + // Unique identifier for the package + Slug string `json:"slug"` + // Current status of the package + Status PackageStatus `json:"status"` +} + +// GetId returns resetPackageResetPackagePackagePayloadResultPackage.Id, and is useful for accessing the field via an interface. +func (v *resetPackageResetPackagePackagePayloadResultPackage) GetId() string { return v.Id } + +// GetSlug returns resetPackageResetPackagePackagePayloadResultPackage.Slug, and is useful for accessing the field via an interface. +func (v *resetPackageResetPackagePackagePayloadResultPackage) GetSlug() string { return v.Slug } + +// GetStatus returns resetPackageResetPackagePackagePayloadResultPackage.Status, and is useful for accessing the field via an interface. +func (v *resetPackageResetPackagePackagePayloadResultPackage) GetStatus() PackageStatus { + return v.Status +} + +// resetPackageResponse is returned by resetPackage on success. +type resetPackageResponse struct { + // Reset a package to its initialized state. This is useful for: + // - Forcing a stuck bundle off the canvas + // - Redeploying a package from scratch + // - Clearing state when delete_state is true + // + // The package's status _will_ be set to INITIALIZED. + // + // By default, the state will not be destroyed. If you want to destroy the state, set delete_state to true. + // By default, the params will not be cleared. If you want to clear the params, set delete_params to true. + // By default, the deployments will not be deleted. If you want to delete the deployments, set delete_deployments to true. + // + // This mutation is an escape hatch and can lose data. + // + // Please make sure to pull any state first. + // + // [Read our manage state guide](https://docs.massdriver.cloud/guides/managing-state#3-pull-existing-state) on setting up your state backend. + // + // ## OpenTofu + // + // > tofu state pull > /somewhere/safe.tfstate + // + // ## Terraform + // + // > terraform state pull > /somewhere/safe.tfstate + ResetPackage resetPackageResetPackagePackagePayload `json:"resetPackage"` +} + +// GetResetPackage returns resetPackageResponse.ResetPackage, and is useful for accessing the field via an interface. +func (v *resetPackageResponse) GetResetPackage() resetPackageResetPackagePackagePayload { + return v.ResetPackage +} + // setPackageVersionResponse is returned by setPackageVersion on success. type setPackageVersionResponse struct { SetPackageVersion setPackageVersionSetPackageVersionPackagePayload `json:"setPackageVersion"` @@ -6255,6 +6360,57 @@ func publishArtifactDefinition( return &data, err } +// The query or mutation executed by resetPackage. +const resetPackage_Operation = ` +mutation resetPackage ($organizationId: ID!, $id: ID!, $deleteState: Boolean!, $deleteParams: Boolean!, $deleteDeployments: Boolean!) { + resetPackage(organizationId: $organizationId, id: $id, deleteState: $deleteState, deleteParams: $deleteParams, deleteDeployments: $deleteDeployments) { + successful + result { + id + slug + status + } + messages { + message + } + } +} +` + +func resetPackage( + ctx context.Context, + client graphql.Client, + organizationId string, + id string, + deleteState bool, + deleteParams bool, + deleteDeployments bool, +) (*resetPackageResponse, error) { + req := &graphql.Request{ + OpName: "resetPackage", + Query: resetPackage_Operation, + Variables: &__resetPackageInput{ + OrganizationId: organizationId, + Id: id, + DeleteState: deleteState, + DeleteParams: deleteParams, + DeleteDeployments: deleteDeployments, + }, + } + var err error + + var data resetPackageResponse + resp := &graphql.Response{Data: &data} + + err = client.MakeRequest( + ctx, + req, + resp, + ) + + return &data, err +} + // The query or mutation executed by setPackageVersion. const setPackageVersion_Operation = ` mutation setPackageVersion ($organizationId: ID!, $id: ID!, $version: String!, $releaseStrategy: ReleaseStrategy) { diff --git a/pkg/commands/pkg/reset.go b/pkg/commands/pkg/reset.go new file mode 100644 index 00000000..ba0d19ee --- /dev/null +++ b/pkg/commands/pkg/reset.go @@ -0,0 +1,19 @@ +package pkg + +import ( + "context" + + "github.com/massdriver-cloud/mass/pkg/api" + "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" +) + +// Resets a package with options to delete state, params, and deployments. +func RunReset(ctx context.Context, mdClient *client.Client, name string) (*api.Package, error) { + pkg, err := api.GetPackage(ctx, mdClient, name) + + if err != nil { + return nil, err + } + + return api.ResetPackage(ctx, mdClient, pkg.ID) +} diff --git a/pkg/commands/pkg/reset_test.go b/pkg/commands/pkg/reset_test.go new file mode 100644 index 00000000..2a06af82 --- /dev/null +++ b/pkg/commands/pkg/reset_test.go @@ -0,0 +1,50 @@ +package pkg_test + +import ( + "net/http" + "testing" + + "github.com/massdriver-cloud/mass/pkg/api" + "github.com/massdriver-cloud/mass/pkg/commands/pkg" + "github.com/massdriver-cloud/mass/pkg/gqlmock" + "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" +) + +func TestRunReset(t *testing.T) { + responses := []gqlmock.ResponseFunc{ + func(req *http.Request) any { + return gqlmock.MockQueryResponse("getPackage", api.Package{ + ID: "pkg-uuid1", + Slug: "ecomm-prod-cache", + Manifest: &api.Manifest{ID: "manifest-id"}, + Environment: &api.Environment{ID: "target-id"}, + }) + }, + func(req *http.Request) any { + return gqlmock.MockMutationResponse("resetPackage", map[string]any{ + "id": "pkg-uuid1", + "slug": "ecomm-prod-cache", + "status": "ready", + }) + }, + } + + mdClient := client.Client{ + GQL: gqlmock.NewClientWithFuncResponseArray(responses), + } + + pkg, err := pkg.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 pkg.Slug != "ecomm-prod-cache" { + t.Errorf("got %v, wanted %v", pkg.Slug, "ecomm-prod-cache") + } + if pkg.Status != "ready" { + t.Errorf("got %v, wanted %v", pkg.Status, "ready") + } +} From 6b92be79f891d44a936f0a73684e4fcb9f0d4855 Mon Sep 17 00:00:00 2001 From: chrisghill Date: Wed, 7 Jan 2026 15:46:43 -0700 Subject: [PATCH 2/2] review changes --- cmd/package.go | 27 +++++++++++++++++++++++++++ docs/generated/mass_package_reset.md | 4 +++- pkg/commands/pkg/reset.go | 2 +- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/cmd/package.go b/cmd/package.go index 15a3f51d..0aca9e5d 100644 --- a/cmd/package.go +++ b/cmd/package.go @@ -15,6 +15,7 @@ import ( "github.com/massdriver-cloud/mass/pkg/api" "github.com/massdriver-cloud/mass/pkg/commands/pkg" "github.com/massdriver-cloud/mass/pkg/files" + "github.com/massdriver-cloud/mass/pkg/prettylogs" "github.com/charmbracelet/glamour" "github.com/charmbracelet/lipgloss" @@ -130,6 +131,7 @@ func NewCmdPkg() *cobra.Command { Args: cobra.ExactArgs(1), RunE: runPkgReset, } + pkgResetCmd.Flags().BoolP("force", "f", false, "Skip confirmation prompt") pkgCmd.AddCommand(pkgConfigureCmd) pkgCmd.AddCommand(pkgDeployCmd) @@ -472,6 +474,11 @@ func runPkgReset(cmd *cobra.Command, args []string) error { packageSlugOrID := args[0] + force, err := cmd.Flags().GetBool("force") + if err != nil { + return err + } + cmd.SilenceUsage = true mdClient, mdClientErr := client.New() @@ -479,6 +486,26 @@ func runPkgReset(cmd *cobra.Command, args []string) error { 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 diff --git a/docs/generated/mass_package_reset.md b/docs/generated/mass_package_reset.md index c83860e2..7db138e4 100644 --- a/docs/generated/mass_package_reset.md +++ b/docs/generated/mass_package_reset.md @@ -26,6 +26,7 @@ Reset and delete the deployment history: mass package reset ecomm-prod-vpc ``` + ``` mass package reset -- [flags] ``` @@ -39,7 +40,8 @@ mass package reset api-prod-db ### Options ``` - -h, --help help for reset + -f, --force Skip confirmation prompt + -h, --help help for reset ``` ### SEE ALSO diff --git a/pkg/commands/pkg/reset.go b/pkg/commands/pkg/reset.go index ba0d19ee..8b1b3bb8 100644 --- a/pkg/commands/pkg/reset.go +++ b/pkg/commands/pkg/reset.go @@ -7,7 +7,7 @@ import ( "github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client" ) -// Resets a package with options to delete state, params, and deployments. +// Resets a package state to 'Initialized'. func RunReset(ctx context.Context, mdClient *client.Client, name string) (*api.Package, error) { pkg, err := api.GetPackage(ctx, mdClient, name)