Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create new Bee Upsert API #525

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,8 @@ gha-creds-*.json

# Ignore environment variable files
.env

# ignore misc vscode metadata folders
target/
project/
.bsp/
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package sherlock

import (
"fmt"
"net/http"

"github.com/broadinstitute/sherlock/sherlock/internal/authentication"
"github.com/broadinstitute/sherlock/sherlock/internal/bee"
"github.com/broadinstitute/sherlock/sherlock/internal/errors"
"github.com/broadinstitute/sherlock/sherlock/internal/models"
"github.com/creasty/defaults"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
)

// environmentsProceduresV3UpsertBee godoc
//
// @summary Get a Bee, new or existing
// @description Creates a Bee Environment, or retreives one that already exists.
// @tags Environments
// @accept json
// @produce json
// @param environment body EnvironmentV3Create true "The Environment to create"
// @success 201 {object} EnvironmentV3
// @failure 400,403,404,407,409,500 {object} errors.ErrorResponse
// @router /api/environments/procedures/v3/upsert-bee [put]
func environmentsProceduresV3UpsertBee(ctx *gin.Context) {
// force auth
db, err := authentication.MustUseDB(ctx)
if err != nil {
return
}

var environmentBody EnvironmentV3Create
// ShouldBindBodyWith used to handle double-reading body
if err = ctx.ShouldBindBodyWith(&environmentBody, binding.JSON); err != nil {
errors.AbortRequest(ctx, fmt.Errorf("(%s) request validation error: %w", errors.BadRequest, err))
return
}

// set default values
if err = defaults.Set(&environmentBody); err != nil {
errors.AbortRequest(ctx, fmt.Errorf("error setting defaults: %w", err))
return
}

// convert the body to the db model
toCreate, err := environmentBody.toModel(db)
if err != nil {
errors.AbortRequest(ctx, err)
return
}

// changesetParsing
var changesetBody ChangesetV3PlanRequest
if err = ctx.ShouldBindBodyWith(&changesetBody, binding.JSON); err != nil {
errors.AbortRequest(ctx, fmt.Errorf("(%s) JSON error parsing body to %T: %w", errors.BadRequest, changesetBody, err))
return
}

// copypasta from plan, abstract this at some point.
var chartReleaseChangesets, environmentChangesets, recreateChangesets []models.Changeset

if chartReleaseChangesets, err = changesetBody.parseChartReleaseEntries(db); err != nil {
errors.AbortRequest(ctx, fmt.Errorf("error handling chart release entries: %w", err))
return
}
if environmentChangesets, err = changesetBody.parseEnvironmentEntries(db); err != nil {
errors.AbortRequest(ctx, fmt.Errorf("error handling environment entries: %w", err))
return
}
if recreateChangesets, err = changesetBody.parseRecreateEntries(db); err != nil {
errors.AbortRequest(ctx, fmt.Errorf("error handling recreate entries: %w", err))
return

}
beeEdits := append(append(chartReleaseChangesets, environmentChangesets...), recreateChangesets...)

// do the thing
beeModel, err := bee.BeeUpsert(toCreate, beeEdits, db)

// populate &result from the db
if err != nil {
errors.AbortRequest(ctx, err)
return
}

// shove it back into JSON
beeJSON := environmentFromModel(beeModel)
ctx.JSON(http.StatusCreated, beeJSON)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package sherlock

import (
"net/http"

"github.com/broadinstitute/sherlock/go-shared/pkg/utils"
"github.com/broadinstitute/sherlock/sherlock/internal/errors"
"github.com/broadinstitute/sherlock/sherlock/internal/models"
"github.com/gin-gonic/gin"
)

func (s *handlerSuite) TestEnvironmentsProceduresV3UpsertBee_bee() {
template := s.TestData.Environment_Swatomation()
chartReleaseToUpdate := s.TestData.ChartRelease_LeonardoProd() // fix this
var got EnvironmentV3
code := s.HandleRequest(
s.NewRequest("PUT", "/api/environments/procedures/v3/upsert-bee", EnvironmentProceduresV3UpsertBee{
TemplateEnvironment: template.Name,
ChartReleases: []ChangesetV3PlanRequestChartReleaseEntry{
{
ChangesetV3Create: ChangesetV3Create{
ChartRelease: chartReleaseToUpdate.Name,
ToAppVersionResolver: utils.PointerTo("exact"),
ToAppVersionExact: utils.PointerTo("some new version"),
},
},
},
}),
&got)
s.Equal(http.StatusCreated, code)
s.NotZero(got.ID)

s.Run("copied chart releases", func() {
var chartReleasesInTemplate []models.ChartRelease
s.NoError(s.DB.Where("environment_id = ?", template.ID).Find(&chartReleasesInTemplate).Error)
s.NotZero(len(chartReleasesInTemplate))
var chartReleasesInNewEnvironment []models.ChartRelease
s.NoError(s.DB.Where("environment_id = ?", got.ID).Find(&chartReleasesInNewEnvironment).Error)
s.Equal(len(chartReleasesInTemplate), len(chartReleasesInNewEnvironment))
})
}

func (s *handlerSuite) TestEnvironmentsProceduresV3UpsertBee_beeDuplicateOk() {
template := s.TestData.Environment_Swatomation()
chartReleaseToUpdate := s.TestData.ChartRelease_LeonardoProd() // fix this
var got EnvironmentV3
code := s.HandleRequest(
s.NewRequest("PUT", "/api/environments/procedures/v3/upsert-bee", EnvironmentProceduresV3UpsertBee{
TemplateEnvironment: template.Name,
ChartReleases: []ChangesetV3PlanRequestChartReleaseEntry{
{
ChangesetV3Create: ChangesetV3Create{
ChartRelease: chartReleaseToUpdate.Name,
ToAppVersionResolver: utils.PointerTo("exact"),
ToAppVersionExact: utils.PointerTo("some new version"),
},
},
},
}),
&got)
s.Equal(http.StatusCreated, code)
s.NotZero(got.ID)

s.Run("copied chart releases", func() {
var chartReleasesInTemplate []models.ChartRelease
s.NoError(s.DB.Where("environment_id = ?", template.ID).Find(&chartReleasesInTemplate).Error)
s.NotZero(len(chartReleasesInTemplate))
var chartReleasesInNewEnvironment []models.ChartRelease
s.NoError(s.DB.Where("environment_id = ?", got.ID).Find(&chartReleasesInNewEnvironment).Error)
s.Equal(len(chartReleasesInTemplate), len(chartReleasesInNewEnvironment))
})

// run the request again with the last name, should return the same ID
var got2 EnvironmentV3
code = s.HandleRequest(
s.NewRequest("PUT", "/api/environments/procedures/v3/upsert-bee", EnvironmentProceduresV3UpsertBee{
TemplateEnvironment: template.Name,
Name: got.Name,
ChartReleases: []ChangesetV3PlanRequestChartReleaseEntry{
{
ChangesetV3Create: ChangesetV3Create{
ChartRelease: chartReleaseToUpdate.Name,
ToAppVersionResolver: utils.PointerTo("exact"),
ToAppVersionExact: utils.PointerTo("some new version"),
},
},
},
}),
&got2)
s.Equal(http.StatusCreated, code)
s.Equal(got.ID, got2.ID)
}

func (s *handlerSuite) TestEnvironmentsProceduresV3UpsertBee_badBody() {
var got errors.ErrorResponse
code := s.HandleRequest(
s.NewRequest("PUT", "/api/environments/procedures/v3/upsert-bee", gin.H{
"name": 123,
}),
&got)
s.Equal(http.StatusBadRequest, code)
s.Equal(errors.BadRequest, got.Type)
s.Contains(got.Message, "name")
}

func (s *handlerSuite) TestEnvironmentsProceduresV3UpsertBee_failToConvertToModel() {
var got errors.ErrorResponse
code := s.HandleRequest(
s.NewRequest("PUT", "/api/environments/procedures/v3/upsert-bee", EnvironmentProceduresV3UpsertBee{
EnvironmentV3Edit: EnvironmentV3Edit{
DefaultCluster: utils.PointerTo("not-found"),
},
}),
&got)
s.Equal(http.StatusNotFound, code)
s.Equal(errors.NotFound, got.Type)
s.Contains(got.Message, "not-found")
}

func (s *handlerSuite) TestEnvironmentProceduresV3UpsertBee_template() {
s.TestData.Chart_Honeycomb()
s.TestData.ChartVersion_Honeycomb_V1()
var got errors.ErrorResponse
code := s.HandleRequest(
s.NewRequest("PUT", "/api/environments/procedures/v3/upsert-bee", EnvironmentProceduresV3UpsertBee{
Base: "swatomation",
Lifecycle: "template",
Name: "tempy-temp",
}),
&got)
s.Equal(http.StatusBadRequest, code)
s.Equal(errors.BadRequest, got.Type)
s.Contains(got.Message, "Lifecycle is not \"dynamic\"")
}

func (s *handlerSuite) TestEnvironmentProceduresV3UpsertBee_staticFail() {
s.TestData.Environment_Swatomation()
var got errors.ErrorResponse
code := s.HandleRequest(
s.NewRequest("PUT", "/api/environments/procedures/v3/upsert-bee", EnvironmentProceduresV3UpsertBee{
Base: "live",
Lifecycle: "static",
Name: "staticy-static",
}),
&got)
s.Equal(http.StatusBadRequest, code)
s.Equal(errors.BadRequest, got.Type)
s.Contains(got.Message, "Lifecycle is not \"dynamic\"")
}

func (s *handlerSuite) TestEnvironmentsProceduresV3UpsertBee_suitability() {
s.TestData.Chart_Honeycomb()
s.TestData.ChartVersion_Honeycomb_V1()
template := s.TestData.Environment_Swatomation()
var got errors.ErrorResponse
code := s.HandleRequest(
s.UseNonSuitableUserFor(s.NewRequest("PUT", "/api/environments/procedures/v3/upsert-bee", EnvironmentProceduresV3UpsertBee{
TemplateEnvironment: template.Name,
Base: "swatomation",
Lifecycle: "dynamic",
Name: "beey-bee",
EnvironmentV3Edit: EnvironmentV3Edit{
RequiresSuitability: utils.PointerTo(true),
},
})),
&got)
s.Equal(http.StatusForbidden, code)
s.Equal(errors.Forbidden, got.Type)
}

func (s *handlerSuite) TestEnvironmentsProceduresV3UpsertBee_suitabilityAllowed() {
s.TestData.Chart_Honeycomb()
s.TestData.ChartVersion_Honeycomb_V1()
template := s.TestData.Environment_Swatomation()
var got EnvironmentV3
code := s.HandleRequest(
s.UseSuitableUserFor(s.NewRequest("PUT", "/api/environments/procedures/v3/upsert-bee", EnvironmentProceduresV3UpsertBee{
TemplateEnvironment: template.Name,
Base: "swatomation",
Lifecycle: "dynamic",
Name: "beey-bee",
EnvironmentV3Edit: EnvironmentV3Edit{
RequiresSuitability: utils.PointerTo(true),
},
})),
&got)
s.Equal(http.StatusCreated, code)
s.NotZero(got.ID)
}
26 changes: 24 additions & 2 deletions sherlock/internal/api/sherlock/environments_v3.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ package sherlock
import (
"database/sql"
"fmt"
"strings"
"time"

"github.com/broadinstitute/sherlock/go-shared/pkg/utils"
"github.com/broadinstitute/sherlock/sherlock/internal/errors"
"github.com/broadinstitute/sherlock/sherlock/internal/models"
"github.com/google/uuid"
"github.com/rs/zerolog/log"
"gorm.io/gorm"
"strings"
"time"
)

type EnvironmentV3 struct {
Expand All @@ -35,6 +36,27 @@ type EnvironmentV3Create struct {
EnvironmentV3Edit
}

type EnvironmentProceduresV3UpsertBee struct {
Base string `json:"base" form:"base"` // Required when creating
AutoPopulateChartReleases *bool `json:"autoPopulateChartReleases" form:"autoPopulateChartReleases" default:"true"` // If true when creating, dynamic environments copy from template and template environments get the honeycomb chart
Lifecycle string `json:"lifecycle" form:"lifecycle" default:"dynamic"`
Name string `json:"name" form:"name"` // When creating, will be calculated if dynamic, required otherwise
TemplateEnvironment string `json:"templateEnvironment" form:"templateEnvironment"` // Required for dynamic environments
UniqueResourcePrefix string `json:"uniqueResourcePrefix" form:"uniqueResourcePrefix"` // When creating, will be calculated if left empty
DefaultNamespace string `json:"defaultNamespace" form:"defaultNamespace"` // When creating, will be calculated if left empty
ValuesName string `json:"valuesName" form:"valuesName"` // When creating, defaults to template name or environment name
EnvironmentV3Edit
ChartReleases []ChangesetV3PlanRequestChartReleaseEntry `json:"chartReleases"`
Environments []ChangesetV3PlanRequestEnvironmentEntry `json:"environments"`
RecreateChangesets []uint `json:"recreateChangesets"`
}

/*
type EnvironmentProceduresV3UpsertBee struct {
EnvironmentV3Create
ChangesetV3PlanRequest
}*/

type EnvironmentV3Edit struct {
DefaultCluster *string `json:"defaultCluster" form:"defaultCluster"`
Owner *string `json:"owner" form:"owner"` // When creating, will default to you
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package sherlock

import (
"net/http"

"github.com/broadinstitute/sherlock/go-shared/pkg/utils"
"github.com/broadinstitute/sherlock/sherlock/internal/errors"
"github.com/broadinstitute/sherlock/sherlock/internal/models"
"github.com/gin-gonic/gin"
"net/http"
)

func (s *handlerSuite) TestEnvironmentsV3Create_badBody() {
Expand Down
1 change: 1 addition & 0 deletions sherlock/internal/api/sherlock/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ func ConfigureRoutes(apiRouter gin.IRoutes) {
apiRouter.DELETE("environments/v3/*selector", environmentsV3Delete)
apiRouter.PATCH("environments/v3/*selector", environmentsV3Edit)
apiRouter.GET("environments/v3/*selector", environmentsV3Get)
apiRouter.PUT("environments/procedures/v3/upsert-bee", environmentsProceduresV3UpsertBee)

apiRouter.GET("github-actions-jobs/v3", githubActionsJobsV3List)
apiRouter.GET("github-actions-jobs/v3/*selector", githubActionsJobsV3Get)
Expand Down
4 changes: 4 additions & 0 deletions sherlock/internal/bee/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# bee package

This package serves as an orchestration package between api and model logic, specifically to deal bee-specific orchestraion tasks

Loading
Loading