Skip to content

Commit

Permalink
Support for new preview environment format (#146)
Browse files Browse the repository at this point in the history
* Preview environments changes

* Output orgs prefixed url, requires that `MASSDRIVER_ORG_ID` be set to the `slug` ID
* Nest params, secrets, and remoteRefs
* early error mode when params and remoteReferences are present

* fmt

* init credentials to empty array

* fix test for params key loc move

* adding project list support

* docs update

* lint

* docs?!?!?

* lint + cost
  • Loading branch information
coryodaniel authored Nov 6, 2024
1 parent 78f3b18 commit d6e6ee7
Show file tree
Hide file tree
Showing 17 changed files with 583 additions and 45 deletions.
20 changes: 11 additions & 9 deletions cmd/preview.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,22 @@ func NewCmdPreview() *cobra.Command {
previewInitCmd.Flags().StringVarP(&previewInitParamsPath, "output", "o", "./preview.json", "Output path for preview environment params file. This file supports bash interpolation and can be manually edited or programatically modified during CI.")

previewDeployCmd := &cobra.Command{
Use: "deploy",
Short: "Deploys a preview environment in your project",
Long: helpdocs.MustRender("preview/deploy"),
RunE: runPreviewDeploy,
Use: "deploy",
Aliases: []string{"apply"},
Short: "Deploys a preview environment in your project",
Long: helpdocs.MustRender("preview/deploy"),
RunE: runPreviewDeploy,
}
previewDeployCmd.Flags().StringVarP(&previewInitParamsPath, "params", "p", previewInitParamsPath, "Path to preview environment configuration file. This file supports bash interpolation.")
previewDeployCmd.Flags().StringVarP(&previewDeployCiContextPath, "ci-context", "c", previewDeployCiContextPath, "Path to GitHub Actions event.json")

previewDecommissionCmd := &cobra.Command{
Use: "decommission $projectTargetSlug",
Short: "Decommissions a preview environment in your project",
Long: helpdocs.MustRender("preview/decommission"),
RunE: runPreviewDecommission,
Args: cobra.ExactArgs(1),
Use: "decommission $projectTargetSlug",
Aliases: []string{"destroy"},
Short: "Decommissions a preview environment in your project",
Long: helpdocs.MustRender("preview/decommission"),
RunE: runPreviewDecommission,
Args: cobra.ExactArgs(1),
}

previewCmd.AddCommand(previewInitCmd)
Expand Down
60 changes: 60 additions & 0 deletions cmd/project.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package cmd

import (
"fmt"
"os"
"text/tabwriter"

"github.com/massdriver-cloud/mass/docs/helpdocs"
"github.com/massdriver-cloud/mass/pkg/api"
"github.com/massdriver-cloud/mass/pkg/config"
"github.com/spf13/cobra"
)

var projCmdHelp = helpdocs.MustRender("project")
var projListCmdHelp = helpdocs.MustRender("project/list")
var projCmd = &cobra.Command{
Use: "project",
Aliases: []string{"prj"},
Short: "Manage Projects",
Long: projCmdHelp,
}

var projListCmd = &cobra.Command{
Use: `list`,
Short: "List projects",
Aliases: []string{"ls"},
Long: projListCmdHelp,
RunE: runProjList,
}

func init() {
rootCmd.AddCommand(projCmd)
projCmd.AddCommand(projListCmd)
}

func runProjList(cmd *cobra.Command, args []string) error {
config, configErr := config.Get()
if configErr != nil {
return configErr
}

client := api.NewClient(config.URL, config.APIKey)

projects, err := api.ListProjects(client, config.OrgID)

w := tabwriter.NewWriter(os.Stdout, 10, 1, 5, ' ', 0)
fmt.Fprintln(w, "SLUG\tNAME\tMONTHLY\tDAILY")

for _, project := range projects {
line := fmt.Sprintf("%s\t%s\t%.2f\t%.2f", project.Slug, project.Name, project.MonthlyAverageCost, project.DailyAverageCost)
fmt.Fprintln(w, line)
}

w.Flush()

// TODO: present UI
// _, err := commands.DeployPackage(client, config.OrgID, name)

return err
}
1 change: 1 addition & 0 deletions docs/generated/mass.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Configure and deploying infrastructure and applications.
* [mass image](/cli/commands/mass_image) - Container image integration Massdriver
* [mass infrastructure](/cli/commands/mass_infrastructure) - Manage infrastructure
* [mass preview](/cli/commands/mass_preview) - Create & deploy preview environments
* [mass project](/cli/commands/mass_project) - Manage Projects
* [mass schema](/cli/commands/mass_schema) - Manage JSON Schemas
* [mass server](/cli/commands/mass_server) - Start the bundle development server
* [mass version](/cli/commands/mass_version) - Version of Mass CLI
29 changes: 29 additions & 0 deletions docs/generated/mass_project.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
id: mass_project.md
slug: /cli/commands/mass_project
title: Mass Project
sidebar_label: Mass Project
---
## mass project

Manage Projects

### Synopsis

# Manage Projects

[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.


### Options

```
-h, --help help for project
```

### SEE ALSO

* [mass](/cli/commands/mass) - Massdriver Cloud CLI
* [mass project list](/cli/commands/mass_project_list) - List projects
30 changes: 30 additions & 0 deletions docs/generated/mass_project_list.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
id: mass_project_list.md
slug: /cli/commands/mass_project_list
title: Mass Project List
sidebar_label: Mass Project List
---
## mass project list

List projects

### Synopsis

# List Projects

Lists Massdriver projects.


```
mass project list [flags]
```

### Options

```
-h, --help help for list
```

### SEE ALSO

* [mass project](/cli/commands/mass_project) - Manage Projects
5 changes: 5 additions & 0 deletions docs/helpdocs/project.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Manage Projects

[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.
3 changes: 3 additions & 0 deletions docs/helpdocs/project/list.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# List Projects

Lists Massdriver projects.
31 changes: 22 additions & 9 deletions pkg/api/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,26 @@ type Environment struct {
URL string
}

const urlTemplate = "https://app.massdriver.cloud/projects/%s/targets/%v"
const urlTemplate = "https://app.massdriver.cloud/orgs/%s/projects/%s/targets/%v"

func DeployPreviewEnvironment(client graphql.Client, orgID string, projectID string, credentials []Credential, packageParams map[string]PreviewPackage, ciContext map[string]interface{}) (*Environment, error) {
// Validate that no package has both params and remote references
for packageName, pkg := range packageParams {
if pkg.Params != nil && len(pkg.RemoteReferences) > 0 {
return nil, fmt.Errorf("package '%s': \"params\" and \"remoteReferences\" are mutually exclusive", packageName)
}
}

func DeployPreviewEnvironment(client graphql.Client, orgID string, projectID string, credentials []Credential, packageParams map[string]interface{}, ciContext map[string]interface{}) (*Environment, error) {
ctx := context.Background()

packageParamsJSON := make(map[string]interface{})
for k, v := range packageParams {
packageParamsJSON[k] = v
}

input := PreviewEnvironmentInput{
Credentials: credentials,
PackageConfigurations: packageParams,
PackageConfigurations: packageParamsJSON,
CiContext: ciContext,
}

Expand All @@ -32,18 +44,19 @@ func DeployPreviewEnvironment(client graphql.Client, orgID string, projectID str
}

if response.DeployPreviewEnvironment.Successful {
return response.DeployPreviewEnvironment.Result.toEnvironment(), nil
return response.DeployPreviewEnvironment.Result.toEnvironment(orgID), nil
}

return nil, NewMutationError("failed to deploy environment", response.DeployPreviewEnvironment.Messages)
}

func (e *deployPreviewEnvironmentDeployPreviewEnvironmentEnvironmentPayloadResultEnvironment) toEnvironment() *Environment {
func (e *deployPreviewEnvironmentDeployPreviewEnvironmentEnvironmentPayloadResultEnvironment) toEnvironment(orgID string) *Environment {
return &Environment{
ID: e.Id,
Slug: e.Slug,

// NOTE: We use IDs here instead of slugs because there is currently a bug in the UI for rendering targets w/ slugs.
URL: fmt.Sprintf(urlTemplate, e.Project.Id, e.Id),
URL: fmt.Sprintf(urlTemplate, orgID, e.Project.Id, e.Id),
}
}

Expand All @@ -59,17 +72,17 @@ func DecommissionPreviewEnvironment(client graphql.Client, orgID string, project
}

if response.DecommissionPreviewEnvironment.Successful {
return response.DecommissionPreviewEnvironment.Result.toEnvironment(), nil
return response.DecommissionPreviewEnvironment.Result.toEnvironment(orgID), nil
}

return nil, NewMutationError("failed to decommission environment", response.DecommissionPreviewEnvironment.Messages)
}

func (e *decommissionPreviewEnvironmentDecommissionPreviewEnvironmentEnvironmentPayloadResultEnvironment) toEnvironment() *Environment {
func (e *decommissionPreviewEnvironmentDecommissionPreviewEnvironmentEnvironmentPayloadResultEnvironment) toEnvironment(orgID string) *Environment {
return &Environment{
ID: e.Id,
Slug: e.Slug,
// NOTE: We use IDs here instead of slugs because there is currently a bug in the UI for rendering targets w/ slugs.
URL: fmt.Sprintf(urlTemplate, e.Project.Id, e.Id),
URL: fmt.Sprintf(urlTemplate, orgID, e.Project.Id, e.Id),
}
}
78 changes: 70 additions & 8 deletions pkg/api/environment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,30 @@ func TestDeployPreviewEnvironment(t *testing.T) {
},
})

confMap := map[string]interface{}{
"network": map[string]interface{}{
"cidr": "10.0.0.0/16",
credentials := []api.Credential{}

packageParams := map[string]api.PreviewPackage{
"network": {
Params: map[string]interface{}{
"cidr": "10.0.0.0/16",
},
},
"cluster": map[string]interface{}{
"maxNodes": 10,

"cluster": {
Params: map[string]interface{}{
"maxNodes": 10,
},
},
}

ciContext := map[string]interface{}{
"pull_request": map[string]interface{}{
"title": "First commit!",
"number": prNumber,
},
}

credentials := []api.Credential{}

environment, err := api.DeployPreviewEnvironment(client, "faux-org-id", "faux-project-id", credentials, confMap, ciContext)
environment, err := api.DeployPreviewEnvironment(client, "faux-org-id", "faux-project-id", credentials, packageParams, ciContext)

if err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -84,3 +90,59 @@ func TestDecommissionPreviewEnvironment(t *testing.T) {
t.Errorf("got %s , wanted %s", got, want)
}
}

func TestDeployPreviewEnvironmentFailsWithBothParamsAndRemoteRefs(t *testing.T) {
prNumber := 69
slug := fmt.Sprintf("p%d", prNumber)

client := gqlmock.NewClientWithSingleJSONResponse(map[string]interface{}{
"data": map[string]interface{}{
"deployPreviewEnvironment": map[string]interface{}{
"result": map[string]interface{}{
"slug": slug,
"id": "envuuid1",
},
"successful": true,
},
},
})

credentials := []api.Credential{}

packageParams := map[string]api.PreviewPackage{
"network": {
Params: map[string]interface{}{
"cidr": "10.0.0.0/16",
},
RemoteReferences: []api.RemoteRef{
{
ArtifactID: "00000000-0000-0000-0000-000000000000",
Field: "some-field",
},
},
},
"cluster": {
Params: map[string]interface{}{
"maxNodes": 9,
},
},
}

ciContext := map[string]interface{}{
"pull_request": map[string]interface{}{
"title": "First commit!",
"number": prNumber,
},
}

_, err := api.DeployPreviewEnvironment(client, "faux-org-id", "faux-project-id", credentials, packageParams, ciContext)

if err == nil {
t.Error("expected error when both params and remote references are set, got nil")
}

expectedError := "package 'network': \"params\" and \"remoteReferences\" are mutually exclusive"
if err.Error() != expectedError {
t.Errorf("got error %q, wanted %q", err.Error(), expectedError)
}
}
26 changes: 23 additions & 3 deletions pkg/api/genqlient.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,31 @@ query getArtifactsByType($organizationId: ID!, $artifactType: String!) {
}
}

query getProjectById($organizationId: ID!, $id: ID!) {
project(organizationId: $organizationId, id: $id) {
query projects($organizationId: ID!){
projects(organizationId: $organizationId){
name
id
defaultParams
slug
description
defaultParams
cost{
monthly{
average{
amount
}
}
daily{
average{
amount
}
}
}
}
}

query getProjectById($organizationId: ID!, $id: ID!) {
project(organizationId: $organizationId, id: $id) {
id, name, defaultParams, slug, description
}
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/api/preview_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ type PreviewConfig struct {
}

type PreviewPackage struct {
Params map[string]interface{} `json:"params"`
Params map[string]interface{} `json:"params,omitempty"`
Secrets []Secret `json:"secrets,omitempty"`
RemoteReferences []RemoteRef `json:"remoteReferences,omitempty"`
}
Expand Down
Loading

0 comments on commit d6e6ee7

Please sign in to comment.