Skip to content
Merged
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
10 changes: 9 additions & 1 deletion cmd/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,15 @@ func renderPackage(pkg *api.Package) error {
return fmt.Errorf("failed to read template: %w", err)
}

tmpl, err := template.New("package").Parse(string(tmplBytes))
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)
}
Expand Down
24 changes: 24 additions & 0 deletions cmd/templates/package.get.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,30 @@

**Environment:** {{.Environment.Slug}}

**Deployed Version:** {{if .DeployedVersion}}{{deref .DeployedVersion}}{{else}}N/A{{end}}

{{if .LatestDeployment -}}
## Latest Deployment

| Field | Value |
|-------|-------|
| ID | {{.LatestDeployment.ID}} |
| Status | {{.LatestDeployment.Status}} |
| Action | {{.LatestDeployment.Action}} |
| Version | {{.LatestDeployment.Version}} |
| Created At | {{.LatestDeployment.CreatedAt.UTC.Format "2006-01-02 15:04:05 UTC"}} |
{{end}}
Comment thread
coryodaniel marked this conversation as resolved.
{{- if .ActiveDeployment -}}
## Active Deployment

| Field | Value |
|-------|-------|
| ID | {{.ActiveDeployment.ID}} |
| Status | {{.ActiveDeployment.Status}} |
| Action | {{.ActiveDeployment.Action}} |
| Version | {{.ActiveDeployment.Version}} |
| Created At | {{.ActiveDeployment.CreatedAt.UTC.Format "2006-01-02 15:04:05 UTC"}} |
{{end}}
## Parameters
```json
{{.ParamsJSON}}
Expand Down
16 changes: 16 additions & 0 deletions internal/api/genqlient.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,23 @@ query getPackage($organizationId: ID!, $id: ID!) {
id
slug
status
# @genqlient(pointer: true)
deployedVersion
params
latestDeployment {
id
status
action
version
createdAt
}
activeDeployment {
id
status
action
version
createdAt
}
artifacts {
id
name
Expand Down
43 changes: 34 additions & 9 deletions internal/api/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,35 @@ import (
"context"
"encoding/json"
"fmt"
"time"

"github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client"
"github.com/mitchellh/mapstructure"
)

// PackageDeployment represents a deployment summary embedded in a package response.
type PackageDeployment struct {
ID string `json:"id" mapstructure:"id"`
Status string `json:"status" mapstructure:"status"`
Action string `json:"action" mapstructure:"action"`
Version string `json:"version" mapstructure:"version"`
CreatedAt time.Time `json:"createdAt" mapstructure:"createdAt"`
}

// Package represents a deployed bundle instance within a Massdriver environment.
type Package struct {
ID string `json:"id" mapstructure:"id"`
Slug string `json:"slug" mapstructure:"slug"`
Status string `json:"status" mapstructure:"status"`
Artifacts []Artifact `json:"artifacts,omitempty" mapstructure:"artifacts"`
RemoteReferences []RemoteReference `json:"remoteReferences,omitempty" mapstructure:"remoteReferences"`
Bundle *Bundle `json:"bundle,omitempty" mapstructure:"bundle,omitempty"`
Params map[string]any `json:"params" mapstructure:"params"`
Manifest *Manifest `json:"manifest" mapstructure:"manifest,omitempty"`
Environment *Environment `json:"environment,omitempty" mapstructure:"environment,omitempty"`
ID string `json:"id" mapstructure:"id"`
Slug string `json:"slug" mapstructure:"slug"`
Status string `json:"status" mapstructure:"status"`
DeployedVersion *string `json:"deployedVersion,omitempty" mapstructure:"deployedVersion"`
LatestDeployment *PackageDeployment `json:"latestDeployment,omitempty" mapstructure:"latestDeployment"`
ActiveDeployment *PackageDeployment `json:"activeDeployment,omitempty" mapstructure:"activeDeployment"`
Artifacts []Artifact `json:"artifacts,omitempty" mapstructure:"artifacts"`
Comment thread
coryodaniel marked this conversation as resolved.
RemoteReferences []RemoteReference `json:"remoteReferences,omitempty" mapstructure:"remoteReferences"`
Bundle *Bundle `json:"bundle,omitempty" mapstructure:"bundle,omitempty"`
Params map[string]any `json:"params" mapstructure:"params"`
Manifest *Manifest `json:"manifest" mapstructure:"manifest,omitempty"`
Environment *Environment `json:"environment,omitempty" mapstructure:"environment,omitempty"`
}

// ParamsJSON returns the package parameters serialized as a pretty-printed JSON string.
Expand All @@ -46,6 +59,18 @@ func toPackage(p any) (*Package, error) {
if err := mapstructure.Decode(p, &pkg); err != nil {
return nil, fmt.Errorf("failed to decode package: %w", err)
}

// mapstructure creates empty structs/pointers for nil values; nil them out
if pkg.DeployedVersion != nil && *pkg.DeployedVersion == "" {
pkg.DeployedVersion = nil
}
if pkg.LatestDeployment != nil && pkg.LatestDeployment.ID == "" {
pkg.LatestDeployment = nil
}
if pkg.ActiveDeployment != nil && pkg.ActiveDeployment.ID == "" {
pkg.ActiveDeployment = nil
}

return &pkg, nil
}

Expand Down
89 changes: 89 additions & 0 deletions internal/api/package_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"github.com/massdriver-cloud/mass/internal/api"
"github.com/massdriver-cloud/mass/internal/gqlmock"
"github.com/massdriver-cloud/massdriver-sdk-go/massdriver/client"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestGetPackage(t *testing.T) {
Expand Down Expand Up @@ -122,3 +124,90 @@ func TestResetPackage(t *testing.T) {
t.Errorf("got %v, wanted %v", pkg.Status, "ready")
}
}

func TestGetPackage_NilDeployments(t *testing.T) {
pkgName := "ecomm-prod-cache"

gqlClient := gqlmock.NewClientWithSingleJSONResponse(map[string]any{
"data": map[string]any{
"package": map[string]any{
"slug": pkgName,
"status": "provisioned",
"deployedVersion": nil,
"latestDeployment": nil,
"activeDeployment": nil,
"bundle": map[string]any{
"id": "bundle-id",
},
"manifest": map[string]any{
"id": "manifest-id",
},
"environment": map[string]any{
"id": "target-id",
},
},
},
})
mdClient := client.Client{
GQL: gqlClient,
}

got, err := api.GetPackage(t.Context(), &mdClient, pkgName)
require.NoError(t, err)

assert.Nil(t, got.DeployedVersion, "DeployedVersion should be nil for never-deployed packages")
assert.Nil(t, got.LatestDeployment, "LatestDeployment should be nil when not present")
assert.Nil(t, got.ActiveDeployment, "ActiveDeployment should be nil when not present")
}

func TestGetPackage_WithDeployments(t *testing.T) {
pkgName := "ecomm-prod-cache"
version := "0.1.0"

gqlClient := gqlmock.NewClientWithSingleJSONResponse(map[string]any{
"data": map[string]any{
"package": map[string]any{
"slug": pkgName,
"status": "provisioned",
"deployedVersion": version,
"latestDeployment": map[string]any{
"id": "deploy-1",
"status": "COMPLETED",
"action": "PROVISION",
"version": version,
"createdAt": "2026-01-15T10:30:00Z",
},
"activeDeployment": map[string]any{
"id": "deploy-1",
"status": "COMPLETED",
"action": "PROVISION",
"version": version,
"createdAt": "2026-01-15T10:30:00Z",
},
"bundle": map[string]any{
"id": "bundle-id",
},
"manifest": map[string]any{
"id": "manifest-id",
},
"environment": map[string]any{
"id": "target-id",
},
},
},
})
mdClient := client.Client{
GQL: gqlClient,
}

got, err := api.GetPackage(t.Context(), &mdClient, pkgName)
require.NoError(t, err)

require.NotNil(t, got.DeployedVersion)
assert.Equal(t, version, *got.DeployedVersion)
require.NotNil(t, got.LatestDeployment)
assert.Equal(t, "deploy-1", got.LatestDeployment.ID)
assert.Equal(t, "COMPLETED", got.LatestDeployment.Status)
require.NotNil(t, got.ActiveDeployment)
assert.Equal(t, "deploy-1", got.ActiveDeployment.ID)
}
58 changes: 8 additions & 50 deletions internal/api/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -281,12 +281,7 @@ type RootQueryType {
): Package

"Finds a package by its naming convention, the name prefix (project-target-manifest) without the random unique suffix."
getPackageByNamingConvention(
organizationId: ID!

"Package ID or {project.slug}-{environment.slug}-{manifest.slug} i.e.: ecomm-staging-database"
name: String!
): Package @deprecated(reason: "Use package(id: $id){}")
getPackageByNamingConvention(organizationId: ID!, name: String!): Package @deprecated(reason: "Use package(id: $id){}")

"""
List all bundle repositories with pagination, sorting, and search capabilities.
Expand Down Expand Up @@ -541,34 +536,13 @@ type RootMutationType {
createManifest(organizationId: ID!, bundleId: ID!, projectId: ID!, name: String!, slug: String!, description: String): ManifestPayload

"Update a manifest"
updateManifest(
organizationId: ID!

"Manifest ID or {project.slug}-{manifest.slug} i.e.: ecomm-database"
id: ID!

name: String!

description: String
): ManifestPayload
updateManifest(organizationId: ID!, id: ID!, name: String!, description: String): ManifestPayload

"Removes a manifest from a project. This will fail if infrastructure is still provisioned in a target."
deleteManifest(
organizationId: ID!

"Manifest ID or {project.slug}-{manifest.slug} i.e.: ecomm-database"
id: ID!
): ManifestPayload
deleteManifest(organizationId: ID!, id: ID!): ManifestPayload

"Set the manifest position in the graph page"
setManifestPosition(
organizationId: ID!

"Manifest ID or {project.slug}-{manifest.slug} i.e.: ecomm-database"
id: ID!

params: GraphPositionParams!
): ManifestPayload
setManifestPosition(organizationId: ID!, id: ID!, params: GraphPositionParams!): ManifestPayload

"Adds a Service Account to a group"
addServiceAccountToGroup(groupId: ID!, organizationId: ID!, serviceAccountId: ID!): ServiceAccountMemberPayload
Expand Down Expand Up @@ -623,7 +597,6 @@ type RootMutationType {
resetPackage(
organizationId: ID!

"Package ID or {project.slug}-{environment.slug}-{manifest.slug} i.e.: ecomm-staging-database"
id: ID!

"Destroy the state of the package"
Expand Down Expand Up @@ -689,24 +662,10 @@ type RootMutationType {
): PackagePayload

"Set a secret value for the package."
setPackageSecret(
organizationId: ID!

"Package ID or {project.slug}-{environment.slug}-{manifest.slug} i.e.: ecomm-staging-database"
id: ID!

input: SetSecretValueInput!
): SecretMetadataPayload
setPackageSecret(organizationId: ID!, id: ID!, input: SetSecretValueInput!): SecretMetadataPayload

"Remove a secret value from the package."
unsetPackageSecret(
organizationId: ID!

"Package ID or {project.slug}-{environment.slug}-{manifest.slug} i.e.: ecomm-staging-database"
id: ID!

input: UnsetSecretValueInput!
): SecretMetadataPayload
unsetPackageSecret(organizationId: ID!, id: ID!, input: UnsetSecretValueInput!): SecretMetadataPayload

"Create a project"
createProject(organizationId: ID!, name: String!, description: String, slug: String!): ProjectPayload
Expand Down Expand Up @@ -883,7 +842,6 @@ enum ServerMode {
}

enum EmailAuthMethodType {
PASSWORDLESS
PASSKEY
}

Expand Down Expand Up @@ -1399,14 +1357,14 @@ type Organization {

name: String!

slug: String!

createdAt: DateTime

updatedAt: DateTime

attribution: String

slug: String!

"Statistics for projects you have access to within this organization."
dashboard: DashboardStatistics

Expand Down
Loading
Loading