Skip to content

Commit

Permalink
feat: initial work on resource workload support
Browse files Browse the repository at this point in the history
  • Loading branch information
shreddedbacon committed Sep 25, 2024
1 parent 7ad87f8 commit d8ec72b
Show file tree
Hide file tree
Showing 36 changed files with 1,806 additions and 16 deletions.
58 changes: 58 additions & 0 deletions cmd/template_resourceworkloads.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package cmd

import (
"fmt"

"github.com/spf13/cobra"
generator "github.com/uselagoon/build-deploy-tool/internal/generator"
"github.com/uselagoon/build-deploy-tool/internal/helpers"
hpatemplate "github.com/uselagoon/build-deploy-tool/internal/templating/resources/hpa"
pdbtemplate "github.com/uselagoon/build-deploy-tool/internal/templating/resources/pdb"
)

var resourceWorkloadGeneration = &cobra.Command{
Use: "resource-workloads",
Aliases: []string{"rw"},
Short: "Generate the resource workload templates for a Lagoon build",
RunE: func(cmd *cobra.Command, args []string) error {
generator, err := generator.GenerateInput(*rootCmd, true)
if err != nil {
return err
}
return ResourceWorkloadTemplateGeneration(generator)
},
}

// IngressTemplateGeneration .
func ResourceWorkloadTemplateGeneration(g generator.GeneratorInput) error {
lagoonBuild, err := generator.NewGenerator(
g,
)
if err != nil {
return err
}
savedTemplates := g.SavedTemplatesPath

// generate the templates
if g.Debug {
fmt.Println(fmt.Sprintf("Templating HPA manifests to %s", fmt.Sprintf("%s/%s.yaml", savedTemplates, "hpas")))
}
templateYAML, err := hpatemplate.GenerateHPATemplate(*lagoonBuild.BuildValues)
if err != nil {
return fmt.Errorf("couldn't generate template: %v", err)
}
helpers.WriteTemplateFile(fmt.Sprintf("%s/%s.yaml", savedTemplates, "hpas"), templateYAML)
if g.Debug {
fmt.Println(fmt.Sprintf("Templating HPA manifests to %s", fmt.Sprintf("%s/%s.yaml", savedTemplates, "pdbs")))
}
templateYAML, err = pdbtemplate.GeneratePDBTemplate(*lagoonBuild.BuildValues)
if err != nil {
return fmt.Errorf("couldn't generate template: %v", err)
}
helpers.WriteTemplateFile(fmt.Sprintf("%s/%s.yaml", savedTemplates, "pdbs"), templateYAML)
return nil
}

func init() {
templateCmd.AddCommand(resourceWorkloadGeneration)
}
160 changes: 160 additions & 0 deletions cmd/template_resourceworkloads_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package cmd

import (
"fmt"
"os"
"reflect"
"testing"

"github.com/uselagoon/build-deploy-tool/internal/helpers"
"github.com/uselagoon/build-deploy-tool/internal/testdata"

// changes the testing to source from root so paths to test resources must be defined from repo root
_ "github.com/uselagoon/build-deploy-tool/internal/testing"
)

func TestResourceWorkloadTemplateGeneration(t *testing.T) {
tests := []struct {
name string
args testdata.TestData
templatePath string
workloadJSONfile string
resourceWorkloadOverrides string
want string
}{
{
name: "test1 no resource workloads",
args: testdata.GetSeedData(
testdata.TestData{
ProjectName: "example-project",
EnvironmentName: "main",
Branch: "main",
LagoonYAML: "internal/testdata/node/lagoon.yml",
}, true),
templatePath: "testdata/output",
want: "internal/testdata/node/resource-templates/resource1",
},
{
name: "test2 node hpa",
args: testdata.GetSeedData(
testdata.TestData{
ProjectName: "example-project",
EnvironmentName: "main",
Branch: "main",
LagoonYAML: "internal/testdata/node/lagoon.resources1.yml",
}, true),
templatePath: "testdata/output",
workloadJSONfile: "internal/testdata/node/workload.resources1.json",
want: "internal/testdata/node/resource-templates/resource2",
},
{
name: "test3 nginx hpa and pdb",
args: testdata.GetSeedData(
testdata.TestData{
ProjectName: "example-project",
EnvironmentName: "main",
Branch: "main",
LagoonYAML: "internal/testdata/complex/lagoon.resource1.yml",
}, true),
templatePath: "testdata/output",
workloadJSONfile: "internal/testdata/complex/workload.resources1.json",
want: "internal/testdata/complex/resource-templates/resource1",
},
{
name: "test4 nginx hpa and pdb with resource override from feature flag",
args: testdata.GetSeedData(
testdata.TestData{
ProjectName: "example-project",
EnvironmentName: "main",
Branch: "main",
LagoonYAML: "internal/testdata/complex/lagoon.yml",
}, true),
templatePath: "testdata/output",
workloadJSONfile: "internal/testdata/complex/workload.resources2.json",
resourceWorkloadOverrides: "nginx-php:nginx-php-performance",
want: "internal/testdata/complex/resource-templates/resource2",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// set the environment variables from args
err := os.Setenv("LAGOON_FEATURE_FLAG_DEFAULT_WORKLOAD_RESOURCES", helpers.ReadFileBase64Encode(tt.workloadJSONfile))
if err != nil {
t.Errorf("%v", err)
}
err = os.Setenv("LAGOON_FEATURE_FLAG_DEFAULT_WORKLOAD_RESOURCE_TYPES", tt.resourceWorkloadOverrides)
if err != nil {
t.Errorf("%v", err)
}
savedTemplates := tt.templatePath
generator, err := testdata.SetupEnvironment(*rootCmd, savedTemplates, tt.args)
if err != nil {
t.Errorf("%v", err)
}

err = os.MkdirAll(savedTemplates, 0755)
if err != nil {
t.Errorf("couldn't create directory %v: %v", savedTemplates, err)
}

defer os.RemoveAll(savedTemplates)

err = ResourceWorkloadTemplateGeneration(generator)
if err != nil {
t.Errorf("%v", err)
}

files, err := os.ReadDir(savedTemplates)
if err != nil {
t.Errorf("couldn't read directory %v: %v", savedTemplates, err)
}
results, err := os.ReadDir(tt.want)
if err != nil {
t.Errorf("couldn't read directory %v: %v", tt.want, err)
}
if len(files) != len(results) {
for _, f := range files {
f1, err := os.ReadFile(fmt.Sprintf("%s/%s", savedTemplates, f.Name()))
if err != nil {
t.Errorf("couldn't read file %v: %v", savedTemplates, err)
}
fmt.Println(string(f1))
}
t.Errorf("number of generated templates doesn't match results %v/%v: %v", len(files), len(results), err)
}
fCount := 0
for _, f := range files {
for _, r := range results {
if f.Name() == r.Name() {
fCount++
f1, err := os.ReadFile(fmt.Sprintf("%s/%s", savedTemplates, f.Name()))
if err != nil {
t.Errorf("couldn't read file %v: %v", savedTemplates, err)
}
r1, err := os.ReadFile(fmt.Sprintf("%s/%s", tt.want, f.Name()))
if err != nil {
t.Errorf("couldn't read file %v: %v", tt.want, err)
}
if !reflect.DeepEqual(f1, r1) {
fmt.Println(string(f1))
t.Errorf("resulting templates do not match")
}
}
}
}
if fCount != len(files) {
for _, f := range files {
f1, err := os.ReadFile(fmt.Sprintf("%s/%s", savedTemplates, f.Name()))
if err != nil {
t.Errorf("couldn't read file %v: %v", savedTemplates, err)
}
fmt.Println(string(f1))
}
t.Errorf("resulting templates do not match")
}
t.Cleanup(func() {
helpers.UnsetEnvVars([]helpers.EnvironmentVariable{{Name: "LAGOON_FEATURE_FLAG_DEFAULT_INGRESS_CLASS"}})
})
})
}
}
27 changes: 27 additions & 0 deletions internal/generator/buildvalues.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
composetypes "github.com/compose-spec/compose-go/types"
"github.com/uselagoon/build-deploy-tool/internal/dbaasclient"
"github.com/uselagoon/build-deploy-tool/internal/lagoon"
autoscalingv2 "k8s.io/api/autoscaling/v2"
corev1 "k8s.io/api/core/v1"
policyv1 "k8s.io/api/policy/v1"
)

const (
Expand Down Expand Up @@ -83,6 +85,8 @@ type BuildValues struct {
ForcePullImages []string `json:"forcePullImages"`
Volumes []ComposeVolume `json:"volumes,omitempty" description:"stores any additional persistent volume definitions"`
PodAntiAffinity bool `json:"podAntiAffinity"`
ResourceWorkloads map[string]ResourceWorkloads `json:"resourceWorkloads"`
ResourceWorkloadOverrides string `json:"resourceWorkloadOverrides"`
}

type Resources struct {
Expand Down Expand Up @@ -117,6 +121,28 @@ type PodSecurityContext struct {
OnRootMismatch bool `json:"onRootMismatch"`
}

type ResourceWorkloads struct {
ServiceType string `json:"serviceType"`
HPA *HPASpec `json:"hpa,omitempty"`
PDB *PDBSpec `json:"pdb,omitempty"`
Resources []Resource `json:"resources"`
}

type Resource struct {
Name string `json:"name"`
Resources corev1.ResourceRequirements `json:"resources"`
}

type HPASpec struct {
Spec autoscalingv2.HorizontalPodAutoscalerSpec `json:"spec"`
}

type PDBSpec struct {
Spec policyv1.PodDisruptionBudgetSpec `json:"spec"`
}

// type Resources map[string]corev1.ResourceRequirements

type Fastly struct {
ServiceID string `json:"serviceId"`
APISecretName string `json:"apiSecretName"`
Expand Down Expand Up @@ -200,6 +226,7 @@ type ServiceValues struct {
IsDBaaS bool `json:"isDBaaS"`
IsSingle bool `json:"isSingle"`
AdditionalVolumes []ServiceVolume `json:"additonalVolumes,omitempty"`
ResourceWorkload string `json:"resourceWorkload,omitempty"`
}

type ImageBuild struct {
Expand Down
8 changes: 8 additions & 0 deletions internal/generator/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,9 @@ func NewGenerator(
lagoonDBaaSEnvironmentTypes, _ := lagoon.GetLagoonVariable("LAGOON_DBAAS_ENVIRONMENT_TYPES", nil, buildValues.EnvironmentVariables)
buildValues.DBaaSEnvironmentTypeOverrides = lagoonDBaaSEnvironmentTypes

lagoonResourceWorkloads := CheckFeatureFlag("WORKLOAD_RESOURCE_TYPES", buildValues.EnvironmentVariables, generator.Debug)
buildValues.ResourceWorkloadOverrides = lagoonResourceWorkloads

// check autogenerated routes for fastly `LAGOON_FEATURE_FLAG(_FORCE|_DEFAULT)_FASTLY_AUTOGENERATED` using feature flags
// @TODO: eventually deprecate fastly functionality in favour of a more generic implementation
autogeneratedRoutesFastly := CheckFeatureFlag("FASTLY_AUTOGENERATED", buildValues.EnvironmentVariables, generator.Debug)
Expand Down Expand Up @@ -412,6 +415,11 @@ func NewGenerator(
}
/* end backups configuration */

/* calculate resource workloads */
resourceWorkloads, err := getResourcesFromAPIEnvVar(buildValues.EnvironmentVariables, generator.Debug)
buildValues.ResourceWorkloads = *resourceWorkloads
/* end resource workload calculation */

/*
start compose->service configuration
!! IMPORTANT !!
Expand Down
41 changes: 41 additions & 0 deletions internal/generator/resources.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package generator

import (
"encoding/base64"
"encoding/json"
"fmt"

"github.com/uselagoon/build-deploy-tool/internal/lagoon"
)

func getResourcesFromAPIEnvVar(
envVars []lagoon.EnvironmentVariable,
debug bool,
) (*map[string]ResourceWorkloads, error) {
resWorkloads := &map[string]ResourceWorkloads{}
// TODO: this is still to be determined how the data will be consumed from the API, it may eventually come from
// a configmap or some other means, or a combination of configmap and envvar merging
// for now, consume from featureflag var
resourceWorkloadsJSON := CheckFeatureFlag("WORKLOAD_RESOURCES", envVars, debug)
// only from envvar from api, not feature flagable
// resourceWorkloadsJSONvar, _ := lagoon.GetLagoonVariable("LAGOON_WORKLOAD_RESOURCES", []string{"build", "global"}, envVars)
// if resourceWorkloadsJSONvar != nil {
// resourceWorkloadsJSON = resourceWorkloadsJSONvar.Value
// }
if resourceWorkloadsJSON != "" {
if debug {
fmt.Println("Collecting resource workloads from WORKLOAD_RESOURCES variable")
}
// if the routesJSON is populated, then attempt to decode and unmarshal it
rawJSONStr, err := base64.StdEncoding.DecodeString(resourceWorkloadsJSON)
if err != nil {
return nil, fmt.Errorf("couldn't decode resource workloads from Lagoon API, is it actually base64 encoded?: %v", err)
}
rawJSON := []byte(rawJSONStr)
err = json.Unmarshal(rawJSON, resWorkloads)
if err != nil {
return nil, fmt.Errorf("couldn't unmarshal resource workloads from Lagoon API, is it actually JSON that has been base64 encoded?: %v", err)
}
}
return resWorkloads, nil
}
Loading

0 comments on commit d8ec72b

Please sign in to comment.