AR delete surface/Step 6: Version delete mutation

What does this MR do and why?

This is Step 6 of the merged 19-step "monolith artifact delete surface" plan (plan MR: gitlab-org/ops/artifact-registry!2201 (merged)).

It adds one GraphQL mutation: ArtifactRegistryVersionDelete (Mutations::ArtifactRegistry::Versions::Delete), which deletes one version of a Maven or npm package.

It calls the client method #delete_version, which merged in Step 1 (!253251 (merged)). Nothing reached that method from the schema until now. It copies the shape of the merged ArtifactRegistryArtifactDelete mutation from the S05 track (!252723 (merged)).

Behaviour notes:

  • Behind the artifact_registry_ui feature flag, off by default, and marked experiment: { milestone: '19.4' }. No changelog entry, because the flag is off.
  • Takes only name (the repository name) and id. There is no format argument: the format comes from a single repository read that also serves as the payload. There is no kind argument and no kind check, because Artifact Registry reads the repository's kind itself and decides between permanently deleting a hosted version and evicting a remote cached copy.
  • Declares authorize_granular_token skip_reason: :external_service_authorizes, because Artifact Registry is the authorization boundary here, not the monolith.
  • Artifact Registry answers this route with 202 and no body. That is acceptance, not completion. The payload carries no count and does not claim the delete finished. The version list is the source of truth after a delete.
  • A nil repository read raises not-available, so a hidden repository is not confirmed to exist.
  • Versions exist only on Maven and npm repositories. A container (docker/oci) repository is refused on the same #packages? predicate the read resolvers already gate on, rather than falling through to the client's guard_format!, whose message names a format keyword that is not an argument of this mutation.
  • locale/gitlab.pot carries the new translated refusal message. doc/api/graphql/reference/_index.md and public/-/graphql/introspection_result.json are regenerated from the schema.

Scope change. This MR originally also added ArtifactRegistryFileDelete. It has been removed. A reviewer asked on the plan MR whether the UI should offer per-file delete (gitlab-org/ops/artifact-registry!2201 (comment 3773665009)), and the product designer confirmed it should not (gitlab-org/ops/artifact-registry!2201 (comment 3779379507)). The decision was already recorded in the design truth doc (https://gitlab.com/gitlab-org/ci-cd/package-stage/unified-artifact-management/-/blob/main/design/design-truth.md#format-data-model--files-versions-tags-deletes), which says not to offer per-file delete inside a version, because deleting a single file (for example a .pom) corrupts the version for consumers. A follow-up MR removes the now-unused #delete_file method from the client (!253712). Whether Artifact Registry keeps its own delete-file REST route is a separate open question the designer raised with product, and it is not decided here.

Known gap. The plan's Dependencies section says Steps 6 through 10 wait on a spec promotion MR that has not been raised yet. The two Artifact Registry specs that own this surface, docs/specs/monolith/S14-version-list.md and docs/specs/monolith/S06-version-detail.md in https://gitlab.com/gitlab-org/ops/artifact-registry, still place every delete in Phase 2 and specify no GraphQL surface, no arguments, and no error mapping for them. The mutation name, arguments, and error mapping here therefore come from the merged plan rather than from either spec, and the promotion MR will need to record them.

References

Screenshots or screen recordings

This is a GraphQL-only change with no UI. The recording below drives the mutation through the GraphQL explorer (GraphiQL) against a live local Artifact Registry.

The recording runs the five scenarios listed in the validation section below, in order.

How to set up and validate locally

These checks let a reviewer reproduce directly, all run on this branch and passing.

bundle exec rspec ee/spec/graphql/mutations/artifact_registry/versions/delete_spec.rb
# 3 examples, 0 failures

bundle exec rspec ee/spec/requests/api/graphql/mutations/artifact_registry/versions/delete_spec.rb
# 10 examples, 0 failures

bundle exec rubocop ee/app/graphql/mutations/artifact_registry ee/spec/graphql/mutations/artifact_registry ee/spec/requests/api/graphql/mutations/artifact_registry
# 26 files inspected, no offenses detected

bundle exec rake gitlab:graphql:compile_docs
bundle exec rake gitlab:graphql:schema:dump
bundle exec rake gitlab:graphql:generate_all_introspection_schemas

Beyond the specs, the mutation was driven through the GraphQL explorer at /-/graphql-explorer against a live local Artifact Registry (built from the artifact-registry repo, on http://localhost:8080, against this GDK's Postgres, Redis and JWT issuer), on a real npm package published with the real npm CLI. Every block below is captured output from the recorded run.

  1. Publish two real versions into a hosted npm repository:

    npm publish --registry http://localhost:8080/gdk-local-test/npm/fmccawley-s6v-1788532464/
    # + fmccawley-s6v-validation-pkg@1.0.0
    # + fmccawley-s6v-validation-pkg@1.0.1
  2. artifactRegistryVersionDelete on version 1.0.0:

    {
      "data": {
        "artifactRegistryVersionDelete": {
          "errors": [],
          "repository": {
            "name": "fmccawley-s6v-1788532464",
            "format": "NPM",
            "kind": "HOSTED"
          }
        }
      }
    }
  3. Delete the same version again. Artifact Registry answers 404, which surfaces as a mutation error rather than a success:

    {
      "data": {
        "artifactRegistryVersionDelete": {
          "errors": [
            "artifact not found"
          ],
          "repository": null
        }
      }
    }
  4. A repository that does not exist. Its absence is hidden and nothing is deleted:

    {
      "errors": [
        {
          "message": "The resource that you are attempting to access does not exist or you don't have permission to perform this action",
          "path": ["artifactRegistryVersionDelete"]
        }
      ]
    }
  5. A container (docker) repository. Refused before any delete request is sent:

    {
      "errors": [
        {
          "message": "Only Maven and npm repositories have versions.",
          "path": ["artifactRegistryVersionDelete"]
        }
      ]
    }
  6. The schema keeps the version delete input type and no longer carries the file delete one:

    {
      "data": {
        "versionDelete": {
          "name": "ArtifactRegistryVersionDeleteInput"
        },
        "fileDelete": null
      }
    }

Because a 202 is acceptance rather than completion, the version list is the only real proof. Re-reading Artifact Registry's version list after the delete returns only 1.0.1, so 1.0.0 is gone. Both repositories created during the run were deleted afterwards and confirmed gone (a follow-up GET returned 404 on each).

The top-level errors in scenarios 4 and 5 are shown here trimmed to the message and path that GraphiQL displays, because the explorer's response pane wraps them in an Apollo client error object with a long JavaScript stack.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Fiona McCawley

Merge request reports

Loading
Loading