Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 155 additions & 0 deletions docs/components/Cloudsmith.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import { CardGrid, LinkCard } from "@astrojs/starlight/components";
<LinkCard title="Delete Package" href="#delete-package" description="Delete a package from a Cloudsmith repository" />
<LinkCard title="Get Package" href="#get-package" description="Retrieve metadata for a specific Cloudsmith package" />
<LinkCard title="Get Repository" href="#get-repository" description="Fetch details of a Cloudsmith repository" />
<LinkCard title="List Packages" href="#list-packages" description="List packages in a Cloudsmith repository with optional filtering by sync status, quarantine, and vulnerability" />
<LinkCard title="Promote Package" href="#promote-package" description="Copy or move a package from one Cloudsmith repository to another" />
<LinkCard title="Resync Package" href="#resync-package" description="Schedule a Cloudsmith package for resynchronization" />
<LinkCard title="Tag Package" href="#tag-package" description="Add, replace, clear, or remove tags for a Cloudsmith package" />
</CardGrid>
Expand All @@ -35,6 +37,8 @@ SuperPlane authenticates to Cloudsmith using a service account API key, which is
4. Paste the API key below.
5. To give the service access to any repository, click on your Repository and then **Settings** → **Access control → Privileges for specific services**, and add the service with the **Admin** privilege.

> **Note:** The **Promote Package** action (copy or move a package between repositories) requires **Admin** privilege on **both** the source and destination repositories. Make sure the service account has been granted Admin access to every repository it needs to promote packages to or from.

<a id="on-package-created"></a>

## On Package Created
Expand Down Expand Up @@ -330,6 +334,157 @@ Returns the repository object including:
}
```

<a id="list-packages"></a>

## List Packages

**Component key:** `cloudsmith.listPackages`

The List Packages component fetches all packages in a Cloudsmith repository and optionally filters them by sync status, quarantine state, or vulnerability scan result.

### Use Cases

- **Release auditing**: List all fully synchronized packages before a release gate
- **Quarantine monitoring**: Enumerate quarantined packages for a security review workflow
- **Vulnerability triage**: Retrieve packages with detected vulnerabilities and route them to a remediation step
- **Inventory**: Collect a complete snapshot of packages in a repository for reporting

### Configuration

- **Repository** (required): The repository to list packages from, in the form `owner/repository`.
- **Sync Status** (optional): Filter by package synchronization state (`Any`, `Fully Synchronised`, `Awaiting Sync`, `Sync Failed`).
- **Quarantine Status** (optional): Filter by quarantine state (`Any`, `Quarantined`, `Not Quarantined`).
- **Vulnerability Status** (optional): Filter by security scan result (`Any`, `No Vulnerabilities`, `Vulnerabilities Found`).

### Output

Emits a single payload containing a `packages` array. Each entry includes:
- **display_name** / **format**: Package display name and format
- **status_str** / **stage_str**: Human-readable status and sync stage
- **is_quarantined** / **policy_violated**: Quarantine and policy state
- **description** / **license**: Package description and license
- **slug_perm** / **repository**: Permanent identifier and repository slug
- **tags**: Package tags

### Example Output

```json
{
"data": {
"packages": [
{
"description": "Example application container image",
"display_name": "example-app",
"format": "docker",
"is_quarantined": false,
"license": "MIT",
"policy_violated": false,
"repository": "example-repo",
"security_scan_status": "No Vulnerabilities Found",
"slug_perm": "example-pkg-id-1",
"stage_str": "Fully Synchronised",
"status_str": "Available",
"tags": {}
},
{
"description": "Example application container image (previous release)",
"display_name": "example-app",
"format": "docker",
"is_quarantined": true,
"license": "MIT",
"policy_violated": true,
"repository": "example-repo",
"security_scan_status": "Scan Detected Vulnerabilities",
"slug_perm": "example-pkg-id-2",
"stage_str": "Fully Synchronised",
"status_str": "Available",
"tags": {}
}
]
},
"timestamp": "2026-01-15T14:35:00Z",
"type": "cloudsmith.packages.listed"
}
```

<a id="promote-package"></a>

## Promote Package

**Component key:** `cloudsmith.promotePackage`

The Promote Package component copies or moves a package from a source repository to a destination repository within the same Cloudsmith namespace.

### Use Cases

- **Promotion pipelines**: Move a package from staging to production after all checks pass
- **Multi-environment distribution**: Copy a package to multiple target repositories simultaneously
- **Artifact archiving**: Move packages from active repositories to archive repositories
- **Release management**: Promote a vetted version from a dev channel to a release channel

### Configuration

- **Source Repository** (required): The repository that currently holds the package, in the form `owner/repository`.
- **Package** (required): The unique package identifier (`slug_perm`). Supports expressions — use `{{ $['On Package Created'].data.slug_perm }}` to reference an upstream trigger.
- **Destination Repository** (required): The target repository to promote the package into, in the form `owner/repository`.
- **Mode** (required): Whether to `copy` (keep the original) or `move` (remove from source).

### Output

Returns the promoted package as it appears in the destination repository, including:
- **name** / **version**: Package name and version
- **format**: Package format (e.g., `docker`, `python`, `debian`)
- **repository** / **namespace**: Where the package now lives
- **self_webapp_url**: URL to the promoted package in the Cloudsmith web app
- **slug_perm**: Permanent identifier of the package in the destination
- **uploaded_at**: Original upload timestamp

### Example Output

```json
{
"data": {
"cdn_url": "https://dl.cloudsmith.io/basic/example-owner/example-prod-repo/docker/example-app.manifest.json",
"checksum_md5": "00000000000000000000000000000000",
"checksum_sha1": "0000000000000000000000000000000000000000",
"checksum_sha256": "0000000000000000000000000000000000000000000000000000000000000000",
"checksum_sha512": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"display_name": "example-app",
"format": "docker",
"is_quarantined": false,
"is_sync_awaiting": false,
"is_sync_completed": true,
"is_sync_failed": false,
"is_sync_in_flight": false,
"is_sync_in_progress": false,
"name": "example-app",
"namespace": "example-owner",
"policy_violated": false,
"repository": "example-prod-repo",
"security_scan_status": "No Vulnerabilities Found",
"self_html_url": "https://cloudsmith.io/~example-owner/repos/example-prod-repo/packages/detail/docker/example-app/1.2.0/",
"self_url": "https://api.cloudsmith.io/v1/packages/example-owner/example-prod-repo/example-pkg-prod-id/",
"self_webapp_url": "https://app.cloudsmith.com/example-owner/r/example-prod-repo/docker/example-app/1.2.0/example-pkg-prod-id",
"size": 54525952,
"size_str": "52.0 MB",
"slug": "example-app-xyz9",
"slug_perm": "example-pkg-prod-id",
"stage": 9,
"stage_str": "Fully Synchronised",
"status": 2,
"status_str": "Available",
"sync_progress": 100,
"tags": {},
"tags_immutable": {},
"uploaded_at": "2026-01-15T14:30:00Z",
"uploader": "example-user",
"version": "1.2.0"
},
"timestamp": "2026-01-15T15:00:00Z",
"type": "cloudsmith.package.promoted"
}
```

<a id="resync-package"></a>

## Resync Package
Expand Down
55 changes: 55 additions & 0 deletions pkg/integrations/cloudsmith/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,10 +312,20 @@ const packagePageSize = 100

// ListPackages returns all packages in the given repository, following pagination.
func (c *Client) ListPackages(owner, repo string) ([]Package, error) {
return c.ListPackagesWithFilters(owner, repo, "")
}

// ListPackagesWithFilters returns packages filtered by a Lucene-style query string,
// following pagination. An empty query returns all packages.
func (c *Client) ListPackagesWithFilters(owner, repo, query string) ([]Package, error) {
var all []Package

for page := 1; ; page++ {
requestURL := fmt.Sprintf("%s/packages/%s/%s/?page=%d&page_size=%d", c.BaseURL, url.PathEscape(owner), url.PathEscape(repo), page, packagePageSize)
if query != "" {
requestURL += "&query=" + url.QueryEscape(query)
}

responseBody, err := c.execRequest(http.MethodGet, requestURL, nil)
if err != nil {
return nil, err
Expand All @@ -336,6 +346,51 @@ func (c *Client) ListPackages(owner, repo string) ([]Package, error) {
return all, nil
}

// CopyPackage copies a package to a destination repository within the same namespace.
// Returns the copied package in the destination.
func (c *Client) CopyPackage(owner, repo, identifier, destinationRepo string) (*Package, error) {
payload, err := json.Marshal(map[string]string{"destination": destinationRepo})
if err != nil {
return nil, fmt.Errorf("error encoding request: %v", err)
}

requestURL := fmt.Sprintf("%s/packages/%s/%s/%s/copy/", c.BaseURL, url.PathEscape(owner), url.PathEscape(repo), url.PathEscape(identifier))
responseBody, err := c.execRequest(http.MethodPost, requestURL, bytes.NewReader(payload))
if err != nil {
return nil, err
}

var pkg Package
if err := json.Unmarshal(responseBody, &pkg); err != nil {
return nil, fmt.Errorf("error parsing response: %v", err)
}

return &pkg, nil
}

// MovePackage moves a package to a destination repository within the same namespace.
// The package is removed from the source repository after a successful move.
// Returns the moved package in the destination.
func (c *Client) MovePackage(owner, repo, identifier, destinationRepo string) (*Package, error) {
payload, err := json.Marshal(map[string]string{"destination": destinationRepo})
if err != nil {
return nil, fmt.Errorf("error encoding request: %v", err)
}

requestURL := fmt.Sprintf("%s/packages/%s/%s/%s/move/", c.BaseURL, url.PathEscape(owner), url.PathEscape(repo), url.PathEscape(identifier))
responseBody, err := c.execRequest(http.MethodPost, requestURL, bytes.NewReader(payload))
if err != nil {
return nil, err
}

var pkg Package
if err := json.Unmarshal(responseBody, &pkg); err != nil {
return nil, fmt.Errorf("error parsing response: %v", err)
}

return &pkg, nil
}

var errInvalidRepositoryID = errors.New("must be in the form 'owner/repository'")

// parseRepositoryID splits a Cloudsmith repository identifier of the form
Expand Down
4 changes: 4 additions & 0 deletions pkg/integrations/cloudsmith/cloudsmith.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ SuperPlane authenticates to Cloudsmith using a service account API key, which is
3. Click on **Create Service** and copy the generated API key.
4. Paste the API key below.
5. To give the service access to any repository, click on your Repository and then **Settings** → **Access control → Privileges for specific services**, and add the service with the **Admin** privilege.

> **Note:** The **Promote Package** action (copy or move a package between repositories) requires **Admin** privilege on **both** the source and destination repositories. Make sure the service account has been granted Admin access to every repository it needs to promote packages to or from.
`
}

Expand All @@ -74,6 +76,8 @@ func (c *Cloudsmith) Actions() []core.Action {
&ResyncPackage{},
&TagPackage{},
&DeletePackage{},
&ListPackages{},
&PromotePackage{},
}
}

Expand Down
20 changes: 20 additions & 0 deletions pkg/integrations/cloudsmith/example.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,26 @@ func (r *ResyncPackage) ExampleOutput() map[string]any {
return utils.UnmarshalEmbeddedJSON(&exampleOutputResyncPackageOnce, exampleOutputResyncPackageBytes, &exampleOutputResyncPackage)
}

//go:embed example_output_list_packages.json
var exampleOutputListPackagesBytes []byte

var exampleOutputListPackagesOnce sync.Once
var exampleOutputListPackages map[string]any

func (l *ListPackages) ExampleOutput() map[string]any {
return utils.UnmarshalEmbeddedJSON(&exampleOutputListPackagesOnce, exampleOutputListPackagesBytes, &exampleOutputListPackages)
}

//go:embed example_output_promote_package.json
var exampleOutputPromotePackageBytes []byte

var exampleOutputPromotePackageOnce sync.Once
var exampleOutputPromotePackage map[string]any

func (p *PromotePackage) ExampleOutput() map[string]any {
return utils.UnmarshalEmbeddedJSON(&exampleOutputPromotePackageOnce, exampleOutputPromotePackageBytes, &exampleOutputPromotePackage)
}

func (t *TagPackage) ExampleOutput() map[string]any {
return utils.UnmarshalEmbeddedJSON(&exampleOutputTagPackageOnce, exampleOutputTagPackageBytes, &exampleOutputTagPackage)
}
Expand Down
36 changes: 36 additions & 0 deletions pkg/integrations/cloudsmith/example_output_list_packages.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"data": {
"packages": [
{
"description": "Example application container image",
"display_name": "example-app",
"format": "docker",
"is_quarantined": false,
"license": "MIT",
"policy_violated": false,
"repository": "example-repo",
"security_scan_status": "No Vulnerabilities Found",
"slug_perm": "example-pkg-id-1",
"stage_str": "Fully Synchronised",
"status_str": "Available",
"tags": {}
},
{
"description": "Example application container image (previous release)",
"display_name": "example-app",
"format": "docker",
"is_quarantined": true,
"license": "MIT",
"policy_violated": true,
"repository": "example-repo",
"security_scan_status": "Scan Detected Vulnerabilities",
"slug_perm": "example-pkg-id-2",
"stage_str": "Fully Synchronised",
"status_str": "Available",
"tags": {}
}
]
},
"timestamp": "2026-01-15T14:35:00Z",
"type": "cloudsmith.packages.listed"
}
41 changes: 41 additions & 0 deletions pkg/integrations/cloudsmith/example_output_promote_package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"data": {
"cdn_url": "https://dl.cloudsmith.io/basic/example-owner/example-prod-repo/docker/example-app.manifest.json",
"checksum_md5": "00000000000000000000000000000000",
"checksum_sha1": "0000000000000000000000000000000000000000",
"checksum_sha256": "0000000000000000000000000000000000000000000000000000000000000000",
"checksum_sha512": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"display_name": "example-app",
"format": "docker",
"is_quarantined": false,
"is_sync_awaiting": false,
"is_sync_completed": true,
"is_sync_failed": false,
"is_sync_in_flight": false,
"is_sync_in_progress": false,
"name": "example-app",
"namespace": "example-owner",
"policy_violated": false,
"repository": "example-prod-repo",
"security_scan_status": "No Vulnerabilities Found",
"self_html_url": "https://cloudsmith.io/~example-owner/repos/example-prod-repo/packages/detail/docker/example-app/1.2.0/",
"self_url": "https://api.cloudsmith.io/v1/packages/example-owner/example-prod-repo/example-pkg-prod-id/",
"self_webapp_url": "https://app.cloudsmith.com/example-owner/r/example-prod-repo/docker/example-app/1.2.0/example-pkg-prod-id",
"size": 54525952,
"size_str": "52.0 MB",
"slug": "example-app-xyz9",
"slug_perm": "example-pkg-prod-id",
"stage": 9,
"stage_str": "Fully Synchronised",
"status": 2,
"status_str": "Available",
"sync_progress": 100,
"tags": {},
"tags_immutable": {},
"uploaded_at": "2026-01-15T14:30:00Z",
"uploader": "example-user",
"version": "1.2.0"
},
"timestamp": "2026-01-15T15:00:00Z",
"type": "cloudsmith.package.promoted"
}
Loading