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

ROX-18942 - Add operator configuration in fleetshard-sync #1157

Merged
merged 8 commits into from
Aug 11, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion fleetshard/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type Config struct {
CreateAuthProvider bool `env:"CREATE_AUTH_PROVIDER" envDefault:"false"`
MetricsAddress string `env:"FLEETSHARD_METRICS_ADDRESS" envDefault:":8080"`
EgressProxyImage string `env:"EGRESS_PROXY_IMAGE"`
BaseCrdURL string `env:"BASE_CRD_URL" envDefault:"https://raw.githubusercontent.com/stackrox/stackrox/%s/operator/bundle/manifests/"`
DefaultBaseCRDURL string `env:"DEFAULT_BASE_CRD_URL" envDefault:"https://raw.githubusercontent.com/stackrox/stackrox/%s/operator/bundle/manifests/"`
Copy link
Collaborator

@ludydoo ludydoo Aug 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider something like:

Suggested change
DefaultBaseCRDURL string `env:"DEFAULT_BASE_CRD_URL" envDefault:"https://raw.githubusercontent.com/stackrox/stackrox/%s/operator/bundle/manifests/"`
CentralCRDURLTemplate string `env:"CENTRAL_CRD_URL_TEMPLATE" envDefault:"https://raw.githubusercontent.com/stackrox/stackrox/{{ .GitRef }} /operator/bundle/manifests/platform.stackrox.io_centrals.yaml"`

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the variable and replaced it with a constant. We can overwrite the default via GitOps, there is no need for a config as an env variable, it would be duplicated.


ManagedDB ManagedDB
Telemetry Telemetry
Expand Down
108 changes: 108 additions & 0 deletions fleetshard/pkg/central/operator/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package operator

import (
"fmt"
"github.com/golang/glog"
"github.com/stackrox/acs-fleet-manager/internal/dinosaur/pkg/api/private"
"sigs.k8s.io/yaml"
)

func parseConfig(content []byte) (OperatorConfigs, error) {
var out OperatorConfigs
err := yaml.Unmarshal(content, &out)
if err != nil {
return OperatorConfigs{}, fmt.Errorf("unmarshalling operator config %w", err)
}
return out, nil
}

// GetConfig returns the rhacs operator configurations
func GetConfig() OperatorConfigs {
// TODO: Read config from GitOps configuration
glog.Error("Reading RHACS Operator GitOps configuration not implemented yet")
return OperatorConfigs{}
}

// Validate validates the operator configuration and can be used in different life-cycle stages like runtime and deploy time.
func Validate(configs OperatorConfigs) []error {
var errors []error
manager := ACSOperatorManager{
// TODO: align config URL with fleetshard-sync default
DefaultBaseCRDURL: "https://raw.githubusercontent.com/stackrox/stackrox/%s/operator/bundle/manifests/",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this duplicated there ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed it and replaced with a constant

}

// TODO: how to run this locally with the same config as fleet-manager?
manifests, err := manager.RenderChart(configs)
kurlov marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
errors = append(errors, fmt.Errorf("could not render operator helm charts, got invalid configuration: %s", err.Error()))
} else if len(manifests) == 0 {
errors = append(errors, fmt.Errorf("operator chart rendering succeed, but no manifests rendered"))
}

return errors
}

// CRDConfig represents the crd to be installed in the data-plane cluster. The CRD is downloaded automatically
// from the base URL. It takes a GitRef to resolve a GitHub link to the CRD definition.
type CRDConfig struct {
BaseURL string `json:"baseURL,omitempty"`
GitRef string `json:"gitRef"`
}

// OperatorConfigs represents all operators and the CRD which should be installed in a data-plane cluster.
type OperatorConfigs struct {
CRD CRDConfig `json:"crd"`
Configs []OperatorConfig `json:"operators"`
}

// OperatorConfig represents the configuration of an operator.
type OperatorConfig struct {
Image string `json:"image"`
GitRef string `json:"gitRef"`
HelmValues string `json:"helmValues,omitempty"`
}

// ToAPIResponse transforms the config to an private API response.
func (o OperatorConfigs) ToAPIResponse() private.RhacsOperatorConfigs {
apiConfigs := private.RhacsOperatorConfigs{
CRD: private.RhacsOperatorConfigsCrd{
GitRef: o.CRD.GitRef,
BaseURL: o.CRD.BaseURL,
},
}

for _, config := range o.Configs {
apiConfigs.RHACSOperatorConfigs = append(apiConfigs.RHACSOperatorConfigs, config.ToAPIResponse())
}
return apiConfigs
}

// ToAPIResponse converts the internal OperatorConfig to the openapi generated private.RhacsOperatorConfig type.
func (o OperatorConfig) ToAPIResponse() private.RhacsOperatorConfig {
return private.RhacsOperatorConfig{
Image: o.Image,
GitRef: o.GitRef,
HelmValues: o.HelmValues,
}
}

// FromAPIResponse converts an openapi generated model to the internal OperatorConfigs type
func FromAPIResponse(config private.RhacsOperatorConfigs) OperatorConfigs {
var operatorConfigs []OperatorConfig
for _, apiConfig := range config.RHACSOperatorConfigs {
config := OperatorConfig{
Image: apiConfig.Image,
GitRef: apiConfig.GitRef,
HelmValues: apiConfig.HelmValues,
}
operatorConfigs = append(operatorConfigs, config)
}

return OperatorConfigs{
Configs: operatorConfigs,
CRD: CRDConfig{
GitRef: config.CRD.GitRef,
BaseURL: config.CRD.BaseURL,
},
}
}
40 changes: 40 additions & 0 deletions fleetshard/pkg/central/operator/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package operator

import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
)

func getExampleConfig() []byte {
return []byte(`
crd:
baseURL: https://raw.githubusercontent.com/stackrox/stackrox/%s/operator/bundle/manifests/
gitRef: 4.1.1
operators:
- gitRef: 4.1.1
image: "quay.io/rhacs-eng/stackrox-operator:4.1.1"
helmValues: |
operator:
resources:
requests:
memory: 500Mi
cpu: 50m
`)
}

func TestGetOperatorConfig(t *testing.T) {
conf, err := parseConfig(getExampleConfig())
require.NoError(t, err)
assert.Len(t, conf.Configs, 1)
assert.Equal(t, "4.1.1", conf.Configs[0].GitRef)
assert.Equal(t, "quay.io/rhacs-eng/stackrox-operator:4.1.1", conf.Configs[0].Image)
}

func TestValidate(t *testing.T) {
conf, err := parseConfig(getExampleConfig())
require.NoError(t, err)

errors := Validate(conf)
assert.Len(t, errors, 0)
}
87 changes: 52 additions & 35 deletions fleetshard/pkg/central/operator/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import (
containerImage "github.com/containers/image/docker/reference"
"github.com/stackrox/acs-fleet-manager/fleetshard/pkg/central/charts"
"golang.org/x/exp/slices"
"gopkg.in/yaml.v2"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chartutil"
appsv1 "k8s.io/api/apps/v1"
"k8s.io/apimachinery/pkg/api/errors"
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/validation"
ctrlClient "sigs.k8s.io/controller-runtime/pkg/client"
)
Expand All @@ -22,15 +24,9 @@ const (
operatorDeploymentPrefix = "rhacs-operator"
)

// DeploymentConfig represents operator configuration for deployment
type DeploymentConfig struct {
Image string
GitRef string
}

func parseOperatorConfigs(operators []DeploymentConfig) ([]chartutil.Values, error) {
func parseOperatorConfigs(operators OperatorConfigs) ([]chartutil.Values, error) {
var helmValues []chartutil.Values
for _, operator := range operators {
for _, operator := range operators.Configs {
imageReference, err := containerImage.Parse(operator.Image)
if err != nil {
return nil, err
Expand All @@ -51,27 +47,56 @@ func parseOperatorConfigs(operators []DeploymentConfig) ([]chartutil.Values, err
"image": image,
"labelSelector": operator.GitRef,
}

operatorHelmValues := make(map[string]interface{})
err = yaml.Unmarshal([]byte(operator.HelmValues), operatorHelmValues)
if err != nil {
return nil, fmt.Errorf("Unmarshalling Helm values failed for operator %s: %w.", operator.GitRef, err)
}

chartutil.CoalesceTables(operatorValues, operatorHelmValues)
helmValues = append(helmValues, operatorValues)
}
return helmValues, nil
}

// ACSOperatorManager keeps data necessary for managing ACS Operator
type ACSOperatorManager struct {
client ctrlClient.Client
crdURL string
resourcesChart *chart.Chart
client ctrlClient.Client
DefaultBaseCRDURL string
resourcesChart *chart.Chart
}

// InstallOrUpgrade provisions or upgrades an existing ACS Operator from helm chart template
func (u *ACSOperatorManager) InstallOrUpgrade(ctx context.Context, operators []DeploymentConfig, crdTag string) error {
if len(operators) == 0 {
return nil
func (u *ACSOperatorManager) InstallOrUpgrade(ctx context.Context, operators OperatorConfigs) error {
objs, err := u.RenderChart(operators)
if err != nil {
return err
}

for _, obj := range objs {
if obj.GetNamespace() == "" {
obj.SetNamespace(operatorNamespace)
}
err := charts.InstallOrUpdateChart(ctx, obj, u.client)
if err != nil {
return fmt.Errorf("failed to update operator object %w", err)
}
}

return nil

}

// RenderChart renders the operator helm chart manifests
func (u *ACSOperatorManager) RenderChart(operators OperatorConfigs) ([]*unstructured.Unstructured, error) {
if len(operators.Configs) == 0 {
return nil, nil
}

operatorImages, err := parseOperatorConfigs(operators)
if err != nil {
return fmt.Errorf("failed to parse images: %w", err)
return nil, fmt.Errorf("failed to parse images: %w", err)
}
chartVals := chartutil.Values{
"operator": chartutil.Values{
Expand All @@ -80,28 +105,16 @@ func (u *ACSOperatorManager) InstallOrUpgrade(ctx context.Context, operators []D
}

var dynamicTemplatesUrls []string
if crdTag != "" {
dynamicTemplatesUrls = u.generateCRDTemplateUrls(crdTag)
if operators.CRD.GitRef != "" {
dynamicTemplatesUrls = u.generateCRDTemplateUrls(operators.CRD)
}

u.resourcesChart = charts.MustGetChart("rhacs-operator", dynamicTemplatesUrls)
objs, err := charts.RenderToObjects(releaseName, operatorNamespace, u.resourcesChart, chartVals)
if err != nil {
return fmt.Errorf("failed rendering operator chart: %w", err)
}

for _, obj := range objs {
if obj.GetNamespace() == "" {
obj.SetNamespace(operatorNamespace)
}
err := charts.InstallOrUpdateChart(ctx, obj, u.client)
if err != nil {
return fmt.Errorf("failed to update operator object %w", err)
}
return nil, fmt.Errorf("failed rendering operator chart: %w", err)
}

return nil

return objs, nil
}

// ListVersionsWithReplicas returns currently running ACS Operator versions with number of ready replicas
Expand Down Expand Up @@ -168,8 +181,12 @@ func generateDeploymentName(version string) string {
return operatorDeploymentPrefix + "-" + version
}

func (u *ACSOperatorManager) generateCRDTemplateUrls(tag string) []string {
stackroxWithTag := fmt.Sprintf(u.crdURL, tag)
func (u *ACSOperatorManager) generateCRDTemplateUrls(crdConfig CRDConfig) []string {
baseURL := u.DefaultBaseCRDURL
if crdConfig.BaseURL != "" {
baseURL = crdConfig.BaseURL
}
stackroxWithTag := fmt.Sprintf(baseURL, crdConfig.GitRef)
centralCrdURL := stackroxWithTag + "platform.stackrox.io_centrals.yaml"
securedClusterCrdURL := stackroxWithTag + "platform.stackrox.io_securedclusters.yaml"
return []string{centralCrdURL, securedClusterCrdURL}
Expand All @@ -178,7 +195,7 @@ func (u *ACSOperatorManager) generateCRDTemplateUrls(tag string) []string {
// NewACSOperatorManager creates a new ACS Operator Manager
func NewACSOperatorManager(k8sClient ctrlClient.Client, baseCrdURL string) *ACSOperatorManager {
return &ACSOperatorManager{
client: k8sClient,
crdURL: baseCrdURL,
client: k8sClient,
DefaultBaseCRDURL: baseCrdURL,
}
}
Loading