Skip to content

Commit

Permalink
Merge pull request #1261 from posit-dev/mm-config-name-in-api
Browse files Browse the repository at this point in the history
Add config name to API
  • Loading branch information
mmarchetti authored Apr 3, 2024
2 parents c04999a + a6e7285 commit d2602cd
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 4 deletions.
20 changes: 18 additions & 2 deletions extensions/vscode/src/api/types/deployments.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (C) 2023 by Posit Software, PBC.

import { AgentError } from "./error";
import { Configuration } from "./configurations";
import { Configuration, ConfigurationLocation } from "./configurations";
import { SchemaURL } from "./schema";
import { ServerType } from "./accounts";

Expand Down Expand Up @@ -35,6 +35,8 @@ export type PreDeployment = {
state: DeploymentState.NEW;
} & DeploymentRecord;

export type PreDeploymentWithConfig = PreDeployment & ConfigurationLocation;

export type Deployment = {
id: string;
bundleId: string;
Expand All @@ -47,7 +49,11 @@ export type Deployment = {
} & DeploymentRecord &
Configuration;

export type AllDeploymentTypes = Deployment | PreDeployment | DeploymentError;
export type AllDeploymentTypes =
| Deployment
| PreDeployment
| PreDeploymentWithConfig
| DeploymentError;

export function isSuccessful(
d: AllDeploymentTypes | undefined,
Expand Down Expand Up @@ -83,6 +89,16 @@ export function isPreDeployment(
return Boolean(d && d.state === DeploymentState.NEW);
}

export function isPreDeploymentWithConfig(
d: AllDeploymentTypes | undefined,
): d is PreDeploymentWithConfig {
return Boolean(
d &&
d.state === DeploymentState.NEW &&
(d as PreDeploymentWithConfig).configurationName !== undefined,
);
}

export function isSuccessfulPreDeployment(
d: AllDeploymentTypes | undefined,
): d is PreDeployment {
Expand Down
16 changes: 14 additions & 2 deletions internal/services/api/deployment_dto.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@ type preDeploymentDTO struct {
ServerURL string `json:"serverUrl"`
SaveName string `json:"saveName"`
CreatedAt string `json:"createdAt"`
ConfigName string `json:"configurationName,omitempty"`
ConfigPath string `json:"configurationPath,omitempty"`
Error *types.AgentError `json:"deploymentError,omitempty"`
}

type fullDeploymentDTO struct {
deploymentLocation
deployment.Deployment
ConfigPath string `json:"configurationPath,omitempty"`
ConfigPath string `json:"configurationPath"`
SaveName string `json:"saveName"`
}

Expand All @@ -55,6 +57,8 @@ func getConfigPath(base util.AbsolutePath, configName string) util.AbsolutePath

func deploymentAsDTO(d *deployment.Deployment, err error, base util.AbsolutePath, path util.AbsolutePath) any {
saveName := deployment.SaveNameFromPath(path)
configPath := ""

if err != nil {
return &deploymentErrorDTO{
deploymentLocation: deploymentLocation{
Expand All @@ -65,17 +69,23 @@ func deploymentAsDTO(d *deployment.Deployment, err error, base util.AbsolutePath
Error: types.AsAgentError(err),
}
} else if d.ID != "" {
if d.ConfigName != "" {
configPath = getConfigPath(base, d.ConfigName).String()
}
return &fullDeploymentDTO{
deploymentLocation: deploymentLocation{
State: deploymentStateDeployed,
Name: saveName,
Path: path.String(),
},
Deployment: *d,
ConfigPath: getConfigPath(base, d.ConfigName).String(),
ConfigPath: configPath,
SaveName: saveName, // TODO: remove this duplicate (remove frontend references first)
}
} else {
if d.ConfigName != "" {
configPath = getConfigPath(base, d.ConfigName).String()
}
return preDeploymentDTO{
deploymentLocation: deploymentLocation{
State: deploymentStateNew,
Expand All @@ -87,6 +97,8 @@ func deploymentAsDTO(d *deployment.Deployment, err error, base util.AbsolutePath
ServerURL: d.ServerURL,
SaveName: saveName, // TODO: remove this duplicate (remove frontend references first)
CreatedAt: d.CreatedAt,
ConfigName: d.ConfigName,
ConfigPath: configPath,
Error: d.Error,
}
}
Expand Down
20 changes: 20 additions & 0 deletions internal/services/api/post_deployments.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ package api
import (
"encoding/json"
"errors"
"fmt"
"net/http"

"github.com/rstudio/connect-client/internal/accounts"
"github.com/rstudio/connect-client/internal/config"
"github.com/rstudio/connect-client/internal/deployment"
"github.com/rstudio/connect-client/internal/logging"
"github.com/rstudio/connect-client/internal/util"
Expand All @@ -15,6 +17,7 @@ import (

type PostDeploymentsRequestBody struct {
AccountName string `json:"account"`
ConfigName string `json:"config"`
SaveName string `json:"saveName"`
}

Expand Down Expand Up @@ -49,6 +52,7 @@ func PostDeploymentsHandlerFunc(
}
}

// Deployment must not exist
path := deployment.GetDeploymentPath(base, b.SaveName)
exists, err := path.Exists()
if err != nil {
Expand All @@ -60,9 +64,25 @@ func PostDeploymentsHandlerFunc(
return
}

if b.ConfigName != "" {
// Config must exist
configPath := config.GetConfigPath(base, b.ConfigName)
exists, err = configPath.Exists()
if err != nil {
InternalError(w, req, log, err)
return
}
if !exists {
w.WriteHeader(http.StatusUnprocessableEntity)
w.Write([]byte(fmt.Sprintf("configuration %s not found", b.ConfigName)))
return
}
}

d := deployment.New()
d.ServerURL = acct.URL
d.ServerType = acct.ServerType
d.ConfigName = b.ConfigName

err = d.WriteFile(path)
if err != nil {
Expand Down
77 changes: 77 additions & 0 deletions internal/services/api/post_deployments_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"testing"

"github.com/rstudio/connect-client/internal/accounts"
"github.com/rstudio/connect-client/internal/config"
"github.com/rstudio/connect-client/internal/logging"
"github.com/rstudio/connect-client/internal/util"
"github.com/rstudio/connect-client/internal/util/utiltest"
Expand Down Expand Up @@ -48,9 +49,14 @@ func (s *PostDeploymentsSuite) TestPostDeployments() {

h := PostDeploymentsHandlerFunc(s.cwd, logging.New(), lister)

cfg := config.New()
err := cfg.WriteFile(config.GetConfigPath(s.cwd, "myConfig"))
s.NoError(err)

rec := httptest.NewRecorder()
body := strings.NewReader(`{
"account": "myAccount",
"config": "myConfig",
"saveName": "newDeployment"
}`)
req, err := http.NewRequest("POST", "/api/deployments", body)
Expand All @@ -71,11 +77,82 @@ func (s *PostDeploymentsSuite) TestPostDeployments() {

s.Equal("newDeployment", res.Name)
s.Equal("newDeployment", res.SaveName)
s.Equal("myConfig", res.ConfigName)
s.Equal("myConfig.toml", filepath.Base(res.ConfigPath))
s.Equal(accounts.ServerTypeConnect, res.ServerType)
s.Equal(acct.URL, res.ServerURL)
s.Equal(deploymentStateNew, res.State)
}

func (s *PostDeploymentsSuite) TestPostDeploymentsNoConfig() {
lister := &accounts.MockAccountList{}
acct := &accounts.Account{
Name: "myAccount",
URL: "https://connect.example.com",
ServerType: accounts.ServerTypeConnect,
}
lister.On("GetAccountByName", "myAccount").Return(acct, nil)

h := PostDeploymentsHandlerFunc(s.cwd, logging.New(), lister)

cfg := config.New()
err := cfg.WriteFile(config.GetConfigPath(s.cwd, "myConfig"))
s.NoError(err)

rec := httptest.NewRecorder()
body := strings.NewReader(`{
"account": "myAccount",
"saveName": "newDeployment"
}`)
req, err := http.NewRequest("POST", "/api/deployments", body)
s.NoError(err)
h(rec, req)

s.Equal(http.StatusOK, rec.Result().StatusCode)
s.Equal("application/json", rec.Header().Get("content-type"))

res := preDeploymentDTO{}
dec := json.NewDecoder(rec.Body)
dec.DisallowUnknownFields()
s.NoError(dec.Decode(&res))

actualPath, err := util.NewPath(res.Path, s.cwd.Fs()).Rel(s.cwd)
s.NoError(err)
s.Equal(filepath.Join(".posit", "publish", "deployments", "newDeployment.toml"), actualPath.String())

s.Equal("newDeployment", res.Name)
s.Equal("newDeployment", res.SaveName)
s.Equal("", res.ConfigName)
s.Equal("", res.ConfigPath)
s.Equal(accounts.ServerTypeConnect, res.ServerType)
s.Equal(acct.URL, res.ServerURL)
s.Equal(deploymentStateNew, res.State)
}

func (s *PostDeploymentsSuite) TestPostDeploymentsNonexistentConfig() {
lister := &accounts.MockAccountList{}
acct := &accounts.Account{
Name: "myAccount",
URL: "https://connect.example.com",
ServerType: accounts.ServerTypeConnect,
}
lister.On("GetAccountByName", "myAccount").Return(acct, nil)

h := PostDeploymentsHandlerFunc(s.cwd, logging.New(), lister)

rec := httptest.NewRecorder()
body := strings.NewReader(`{
"account": "myAccount",
"config": "myConfig",
"saveName": "newDeployment"
}`)
req, err := http.NewRequest("POST", "/api/deployments", body)
s.NoError(err)
h(rec, req)

s.Equal(http.StatusUnprocessableEntity, rec.Result().StatusCode)
}

func (s *PostDeploymentsSuite) TestPostDeploymentsBadRequest() {
h := PostDeploymentsHandlerFunc(s.cwd, logging.New(), nil)

Expand Down

0 comments on commit d2602cd

Please sign in to comment.