Skip to content

Commit

Permalink
feat: Add parameter querying to the API (#143)
Browse files Browse the repository at this point in the history
  • Loading branch information
kylecarbs committed Feb 4, 2022
1 parent 75468fa commit c3bae67
Show file tree
Hide file tree
Showing 7 changed files with 225 additions and 0 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"nolint",
"nosec",
"oneof",
"parameterscopeid",
"protobuf",
"provisionerd",
"provisionersdk",
Expand Down
4 changes: 4 additions & 0 deletions coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ func New(options *Options) http.Handler {
r.Use(httpmw.ExtractProjectParam(options.Database))
r.Get("/", api.projectByOrganization)
r.Get("/workspaces", api.workspacesByProject)
r.Route("/parameters", func(r chi.Router) {
r.Get("/", api.parametersByProject)
r.Post("/", api.postParametersByProject)
})
r.Route("/history", func(r chi.Router) {
r.Get("/", api.projectHistoryByOrganization)
r.Post("/", api.postProjectHistoryByOrganization)
Expand Down
107 changes: 107 additions & 0 deletions coderd/parameters.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package coderd

import (
"database/sql"
"errors"
"fmt"
"net/http"
"time"

"github.com/go-chi/render"
"github.com/google/uuid"

"github.com/coder/coder/database"
"github.com/coder/coder/httpapi"
)

// CreateParameterValueRequest is used to create a new parameter value for a scope.
type CreateParameterValueRequest struct {
Name string `json:"name"`
SourceValue string `json:"source_value"`
SourceScheme database.ParameterSourceScheme `json:"source_scheme"`
DestinationScheme database.ParameterDestinationScheme `json:"destination_scheme"`
DestinationValue string `json:"destination_value"`
}

// ParameterValue represents a set value for the scope.
type ParameterValue struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Scope database.ParameterScope `json:"scope"`
ScopeID string `json:"scope_id"`
SourceScheme database.ParameterSourceScheme `json:"source_scheme"`
DestinationScheme database.ParameterDestinationScheme `json:"destination_scheme"`
DestinationValue string `json:"destination_value"`
}

// Abstracts creating parameters into a single request/response format.
// Callers are in charge of validating the requester has permissions to
// perform the creation.
func postParameterValueForScope(rw http.ResponseWriter, r *http.Request, db database.Store, scope database.ParameterScope, scopeID string) {
var createRequest CreateParameterValueRequest
if !httpapi.Read(rw, r, &createRequest) {
return
}
parameterValue, err := db.InsertParameterValue(r.Context(), database.InsertParameterValueParams{
ID: uuid.New(),
Name: createRequest.Name,
CreatedAt: database.Now(),
UpdatedAt: database.Now(),
Scope: scope,
ScopeID: scopeID,
SourceScheme: createRequest.SourceScheme,
SourceValue: createRequest.SourceValue,
DestinationScheme: createRequest.DestinationScheme,
DestinationValue: createRequest.DestinationValue,
})
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("insert parameter value: %s", err),
})
return
}

render.Status(r, http.StatusCreated)
render.JSON(rw, r, parameterValue)
}

// Abstracts returning parameters for a scope into a standardized
// request/response format. Callers are responsible for checking
// requester permissions.
func parametersForScope(rw http.ResponseWriter, r *http.Request, db database.Store, req database.GetParameterValuesByScopeParams) {
parameterValues, err := db.GetParameterValuesByScope(r.Context(), req)
if errors.Is(err, sql.ErrNoRows) {
err = nil
parameterValues = []database.ParameterValue{}
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get parameter values: %s", err),
})
return
}

apiParameterValues := make([]ParameterValue, 0, len(parameterValues))
for _, parameterValue := range parameterValues {
apiParameterValues = append(apiParameterValues, convertParameterValue(parameterValue))
}

render.Status(r, http.StatusOK)
render.JSON(rw, r, apiParameterValues)
}

func convertParameterValue(parameterValue database.ParameterValue) ParameterValue {
return ParameterValue{
ID: parameterValue.ID,
Name: parameterValue.Name,
CreatedAt: parameterValue.CreatedAt,
UpdatedAt: parameterValue.UpdatedAt,
Scope: parameterValue.Scope,
ScopeID: parameterValue.ScopeID,
SourceScheme: parameterValue.SourceScheme,
DestinationScheme: parameterValue.DestinationScheme,
DestinationValue: parameterValue.DestinationValue,
}
}
18 changes: 18 additions & 0 deletions coderd/projects.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,21 @@ func (api *api) workspacesByProject(rw http.ResponseWriter, r *http.Request) {
render.Status(r, http.StatusOK)
render.JSON(rw, r, apiWorkspaces)
}

// Creates parameters for a project.
// This should validate the calling user has permissions!
func (api *api) postParametersByProject(rw http.ResponseWriter, r *http.Request) {
project := httpmw.ProjectParam(r)

postParameterValueForScope(rw, r, api.Database, database.ParameterScopeProject, project.ID.String())
}

// Lists parameters for a project.
func (api *api) parametersByProject(rw http.ResponseWriter, r *http.Request) {
project := httpmw.ProjectParam(r)

parametersForScope(rw, r, api.Database, database.GetParameterValuesByScopeParams{
Scope: database.ParameterScopeProject,
ScopeID: project.ID.String(),
})
}
32 changes: 32 additions & 0 deletions coderd/projects_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,36 @@ func TestProjects(t *testing.T) {
_, err = server.Client.Project(context.Background(), user.Organization, project.Name)
require.NoError(t, err)
})

t.Run("Parameters", func(t *testing.T) {
t.Parallel()
server := coderdtest.New(t)
user := server.RandomInitialUser(t)
project, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{
Name: "someproject",
Provisioner: database.ProvisionerTypeTerraform,
})
require.NoError(t, err)
_, err = server.Client.ProjectParameters(context.Background(), user.Organization, project.Name)
require.NoError(t, err)
})

t.Run("CreateParameter", func(t *testing.T) {
t.Parallel()
server := coderdtest.New(t)
user := server.RandomInitialUser(t)
project, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{
Name: "someproject",
Provisioner: database.ProvisionerTypeTerraform,
})
require.NoError(t, err)
_, err = server.Client.CreateProjectParameter(context.Background(), user.Organization, project.Name, coderd.CreateParameterValueRequest{
Name: "hi",
SourceValue: "tomato",
SourceScheme: database.ParameterSourceSchemeData,
DestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable,
DestinationValue: "moo",
})
require.NoError(t, err)
})
}
28 changes: 28 additions & 0 deletions codersdk/projects.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,31 @@ func (c *Client) CreateProjectHistory(ctx context.Context, organization, project
var projectVersion coderd.ProjectHistory
return projectVersion, json.NewDecoder(res.Body).Decode(&projectVersion)
}

// ProjectParameters returns parameters scoped to a project.
func (c *Client) ProjectParameters(ctx context.Context, organization, project string) ([]coderd.ParameterValue, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/projects/%s/%s/parameters", organization, project), nil)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, readBodyAsError(res)
}
var params []coderd.ParameterValue
return params, json.NewDecoder(res.Body).Decode(&params)
}

// CreateProjectParameter creates a new parameter value scoped to a project.
func (c *Client) CreateProjectParameter(ctx context.Context, organization, project string, req coderd.CreateParameterValueRequest) (coderd.ParameterValue, error) {
res, err := c.request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/projects/%s/%s/parameters", organization, project), req)
if err != nil {
return coderd.ParameterValue{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusCreated {
return coderd.ParameterValue{}, readBodyAsError(res)
}
var param coderd.ParameterValue
return param, json.NewDecoder(res.Body).Decode(&param)
}
35 changes: 35 additions & 0 deletions codersdk/projects_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,39 @@ func TestProjects(t *testing.T) {
_, err = server.Client.ProjectHistory(context.Background(), user.Organization, project.Name, history.Name)
require.NoError(t, err)
})

t.Run("Parameters", func(t *testing.T) {
t.Parallel()
server := coderdtest.New(t)
user := server.RandomInitialUser(t)
project, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{
Name: "someproject",
Provisioner: database.ProvisionerTypeTerraform,
})
require.NoError(t, err)
params, err := server.Client.ProjectParameters(context.Background(), user.Organization, project.Name)
require.NoError(t, err)
require.NotNil(t, params)
require.Len(t, params, 0)
})

t.Run("CreateParameter", func(t *testing.T) {
t.Parallel()
server := coderdtest.New(t)
user := server.RandomInitialUser(t)
project, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{
Name: "someproject",
Provisioner: database.ProvisionerTypeTerraform,
})
require.NoError(t, err)
param, err := server.Client.CreateProjectParameter(context.Background(), user.Organization, project.Name, coderd.CreateParameterValueRequest{
Name: "hi",
SourceValue: "tomato",
SourceScheme: database.ParameterSourceSchemeData,
DestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable,
DestinationValue: "moo",
})
require.NoError(t, err)
require.Equal(t, "hi", param.Name)
})
}

0 comments on commit c3bae67

Please sign in to comment.