snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
This commit is contained in:
447
server/compose/service/chart.go
Normal file
447
server/compose/service/chart.go
Normal file
@@ -0,0 +1,447 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/actionlog"
|
||||
"github.com/cortezaproject/corteza/server/pkg/errors"
|
||||
"github.com/cortezaproject/corteza/server/pkg/handle"
|
||||
"github.com/cortezaproject/corteza/server/pkg/id"
|
||||
"github.com/cortezaproject/corteza/server/pkg/label"
|
||||
"github.com/cortezaproject/corteza/server/pkg/locale"
|
||||
"github.com/cortezaproject/corteza/server/store"
|
||||
)
|
||||
|
||||
type (
|
||||
chart struct {
|
||||
actionlog actionlog.Recorder
|
||||
ac chartAccessController
|
||||
store store.Storer
|
||||
locale ResourceTranslationsManagerService
|
||||
}
|
||||
|
||||
chartAccessController interface {
|
||||
CanManageResourceTranslations(ctx context.Context) bool
|
||||
CanSearchChartsOnNamespace(context.Context, *types.Namespace) bool
|
||||
CanReadNamespace(context.Context, *types.Namespace) bool
|
||||
CanCreateChartOnNamespace(context.Context, *types.Namespace) bool
|
||||
CanReadChart(context.Context, *types.Chart) bool
|
||||
CanUpdateChart(context.Context, *types.Chart) bool
|
||||
CanDeleteChart(context.Context, *types.Chart) bool
|
||||
}
|
||||
|
||||
chartUpdateHandler func(ctx context.Context, ns *types.Namespace, c *types.Chart) (chartChanges, error)
|
||||
|
||||
chartChanges uint8
|
||||
)
|
||||
|
||||
const (
|
||||
chartUnchanged chartChanges = 0
|
||||
chartChanged chartChanges = 1
|
||||
chartLabelsChanged chartChanges = 2
|
||||
)
|
||||
|
||||
func Chart() *chart {
|
||||
return &chart{
|
||||
ac: DefaultAccessControl,
|
||||
actionlog: DefaultActionlog,
|
||||
store: DefaultStore,
|
||||
locale: DefaultResourceTranslation,
|
||||
}
|
||||
}
|
||||
|
||||
func (svc chart) Find(ctx context.Context, filter types.ChartFilter) (set types.ChartSet, f types.ChartFilter, err error) {
|
||||
var (
|
||||
aProps = &chartActionProps{filter: &filter}
|
||||
ns *types.Namespace
|
||||
)
|
||||
|
||||
// For each fetched item, store backend will check if it is valid or not
|
||||
filter.Check = func(res *types.Chart) (bool, error) {
|
||||
if !svc.ac.CanReadChart(ctx, res) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
err = func() error {
|
||||
ns, err = loadNamespace(ctx, svc.store, filter.NamespaceID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aProps.setNamespace(ns)
|
||||
if !svc.ac.CanSearchChartsOnNamespace(ctx, ns) {
|
||||
return ChartErrNotAllowedToSearch()
|
||||
}
|
||||
|
||||
if len(filter.Labels) > 0 {
|
||||
filter.LabeledIDs, err = label.Search(
|
||||
ctx,
|
||||
svc.store,
|
||||
types.Chart{}.LabelResourceKind(),
|
||||
filter.Labels,
|
||||
id.Uints(filter.ChartID...)...,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// labels specified but no labeled resources found
|
||||
if len(filter.LabeledIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if set, f, err = store.SearchComposeCharts(ctx, svc.store, filter); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = label.Load(ctx, svc.store, toLabeledCharts(set)...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
set.Walk(func(c *types.Chart) error {
|
||||
svc.proc(ctx, c)
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
}()
|
||||
|
||||
return set, f, svc.recordAction(ctx, aProps, ChartActionSearch, err)
|
||||
}
|
||||
|
||||
func (svc chart) FindByID(ctx context.Context, namespaceID, chartID uint64) (c *types.Chart, err error) {
|
||||
return svc.lookup(ctx, namespaceID, func(aProps *chartActionProps) (*types.Chart, error) {
|
||||
return loadChart(ctx, svc.store, namespaceID, chartID)
|
||||
})
|
||||
}
|
||||
|
||||
func (svc chart) FindByHandle(ctx context.Context, namespaceID uint64, h string) (c *types.Chart, err error) {
|
||||
return svc.lookup(ctx, namespaceID, func(aProps *chartActionProps) (*types.Chart, error) {
|
||||
if !handle.IsValid(h) {
|
||||
return nil, ChartErrInvalidHandle()
|
||||
}
|
||||
|
||||
aProps.chart.Handle = h
|
||||
return store.LookupComposeChartByNamespaceIDHandle(ctx, svc.store, namespaceID, h)
|
||||
})
|
||||
}
|
||||
|
||||
func (svc chart) proc(ctx context.Context, c *types.Chart) {
|
||||
if svc.locale == nil || svc.locale.Locale() == nil {
|
||||
return
|
||||
}
|
||||
|
||||
tag := locale.GetAcceptLanguageFromContext(ctx)
|
||||
c.DecodeTranslations(svc.locale.Locale().ResourceTranslations(tag, c.ResourceTranslation()))
|
||||
}
|
||||
|
||||
func (svc chart) Create(ctx context.Context, new *types.Chart) (*types.Chart, error) {
|
||||
var (
|
||||
err error
|
||||
ns *types.Namespace
|
||||
aProps = &chartActionProps{chart: new}
|
||||
)
|
||||
|
||||
err = store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
|
||||
if !handle.IsValid(new.Handle) {
|
||||
return ChartErrInvalidHandle()
|
||||
}
|
||||
|
||||
if ns, err = loadNamespace(ctx, s, new.NamespaceID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aProps.setNamespace(ns)
|
||||
|
||||
if !svc.ac.CanCreateChartOnNamespace(ctx, ns) {
|
||||
return ChartErrNotAllowedToCreate()
|
||||
}
|
||||
|
||||
if err = svc.uniqueCheck(ctx, new); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
new.ID = nextID()
|
||||
new.CreatedAt = *now()
|
||||
new.UpdatedAt = nil
|
||||
new.DeletedAt = nil
|
||||
|
||||
// generate config element IDs
|
||||
new.Config.GenerateIDs(nextID)
|
||||
|
||||
aProps.setChanged(new)
|
||||
|
||||
if err = store.CreateComposeChart(ctx, s, new); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = updateTranslations(ctx, svc.ac, svc.locale, new.EncodeTranslations()...); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = label.Create(ctx, s, new); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return new, svc.recordAction(ctx, aProps, ChartActionCreate, err)
|
||||
}
|
||||
|
||||
func (svc chart) Update(ctx context.Context, upd *types.Chart) (c *types.Chart, err error) {
|
||||
return svc.updater(ctx, upd.NamespaceID, upd.ID, ChartActionUpdate, svc.handleUpdate(ctx, upd))
|
||||
}
|
||||
|
||||
func (svc chart) DeleteByID(ctx context.Context, namespaceID, chartID uint64) error {
|
||||
return trim1st(svc.updater(ctx, namespaceID, chartID, ChartActionDelete, svc.handleDelete))
|
||||
}
|
||||
|
||||
func (svc chart) UndeleteByID(ctx context.Context, namespaceID, chartID uint64) error {
|
||||
return trim1st(svc.updater(ctx, namespaceID, chartID, ChartActionUndelete, svc.handleUndelete))
|
||||
}
|
||||
|
||||
// lookup fn() orchestrates chart lookup, namespace preload and check
|
||||
func (svc chart) lookup(ctx context.Context, namespaceID uint64, lookup func(*chartActionProps) (*types.Chart, error)) (c *types.Chart, err error) {
|
||||
var aProps = &chartActionProps{chart: &types.Chart{NamespaceID: namespaceID}}
|
||||
|
||||
err = func() error {
|
||||
if ns, err := loadNamespace(ctx, svc.store, namespaceID); err != nil {
|
||||
return err
|
||||
} else {
|
||||
aProps.setNamespace(ns)
|
||||
}
|
||||
|
||||
if c, err = lookup(aProps); errors.IsNotFound(err) {
|
||||
return ChartErrNotFound()
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aProps.setChart(c)
|
||||
|
||||
if !svc.ac.CanReadChart(ctx, c) {
|
||||
return ChartErrNotAllowedToRead()
|
||||
}
|
||||
|
||||
svc.proc(ctx, c)
|
||||
|
||||
if err = label.Load(ctx, svc.store, c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
|
||||
return c, svc.recordAction(ctx, aProps, ChartActionLookup, err)
|
||||
}
|
||||
|
||||
func (svc chart) updater(ctx context.Context, namespaceID, chartID uint64, action func(...*chartActionProps) *chartAction, fn chartUpdateHandler) (*types.Chart, error) {
|
||||
var (
|
||||
changes chartChanges
|
||||
ns *types.Namespace
|
||||
c *types.Chart
|
||||
aProps = &chartActionProps{chart: &types.Chart{ID: chartID, NamespaceID: namespaceID}}
|
||||
err error
|
||||
)
|
||||
|
||||
err = store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
|
||||
ns, c, err = loadChartCombo(ctx, s, namespaceID, chartID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = label.Load(ctx, svc.store, c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aProps.setNamespace(ns)
|
||||
aProps.setChanged(c)
|
||||
|
||||
if changes, err = fn(ctx, ns, c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// generate config element IDs if missing
|
||||
c.Config.GenerateIDs(nextID)
|
||||
|
||||
if changes&chartChanged > 0 {
|
||||
if err = store.UpdateComposeChart(ctx, s, c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = updateTranslations(ctx, svc.ac, svc.locale, c.EncodeTranslations()...); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if changes&chartLabelsChanged > 0 {
|
||||
if err = label.Update(ctx, s, c); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return c, svc.recordAction(ctx, aProps, action, err)
|
||||
}
|
||||
|
||||
func (svc chart) uniqueCheck(ctx context.Context, c *types.Chart) (err error) {
|
||||
if c.Handle != "" {
|
||||
if e, _ := store.LookupComposeChartByNamespaceIDHandle(ctx, svc.store, c.NamespaceID, c.Handle); e != nil && e.ID != c.ID {
|
||||
return ChartErrHandleNotUnique()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc chart) handleUpdate(ctx context.Context, upd *types.Chart) chartUpdateHandler {
|
||||
return func(ctx context.Context, ns *types.Namespace, res *types.Chart) (changes chartChanges, err error) {
|
||||
if isStale(upd.UpdatedAt, res.UpdatedAt, res.CreatedAt) {
|
||||
return chartUnchanged, ChartErrStaleData()
|
||||
}
|
||||
|
||||
if upd.Handle != res.Handle && !handle.IsValid(upd.Handle) {
|
||||
return chartUnchanged, ChartErrInvalidHandle()
|
||||
}
|
||||
|
||||
if err := svc.uniqueCheck(ctx, upd); err != nil {
|
||||
return chartUnchanged, err
|
||||
}
|
||||
|
||||
if !svc.ac.CanUpdateChart(ctx, res) {
|
||||
return chartUnchanged, ChartErrNotAllowedToUpdate()
|
||||
}
|
||||
|
||||
if res.Name != upd.Name {
|
||||
changes |= chartChanged
|
||||
res.Name = upd.Name
|
||||
}
|
||||
|
||||
if res.Handle != upd.Handle {
|
||||
changes |= chartChanged
|
||||
res.Handle = upd.Handle
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(upd.Config, res.Config) {
|
||||
changes |= chartChanged
|
||||
res.Config = upd.Config
|
||||
}
|
||||
|
||||
// Assure ReportIDs
|
||||
for i, r := range res.Config.Reports {
|
||||
if r.ReportID == 0 {
|
||||
r.ReportID = nextID()
|
||||
res.Config.Reports[i] = r
|
||||
|
||||
changes |= chartChanged
|
||||
}
|
||||
|
||||
// Ensure chart report metric IDs
|
||||
for j, m := range r.Metrics {
|
||||
if val, ok := m["metricID"]; !ok || val == 0 {
|
||||
m["metricID"] = strconv.FormatUint(nextID(), 10)
|
||||
res.Config.Reports[i].Metrics[j] = m
|
||||
|
||||
changes |= chartChanged
|
||||
}
|
||||
}
|
||||
}
|
||||
if changes&chartChanged > 0 {
|
||||
res.UpdatedAt = now()
|
||||
}
|
||||
|
||||
if upd.Labels != nil {
|
||||
if label.Changed(res.Labels, upd.Labels) {
|
||||
changes |= chartLabelsChanged
|
||||
res.Labels = upd.Labels
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (svc chart) handleDelete(ctx context.Context, ns *types.Namespace, c *types.Chart) (chartChanges, error) {
|
||||
if !svc.ac.CanDeleteChart(ctx, c) {
|
||||
return chartUnchanged, ChartErrNotAllowedToDelete()
|
||||
}
|
||||
|
||||
if c.DeletedAt != nil {
|
||||
// chart already deleted
|
||||
return chartUnchanged, nil
|
||||
}
|
||||
|
||||
c.DeletedAt = now()
|
||||
return chartChanged, nil
|
||||
}
|
||||
|
||||
func (svc chart) handleUndelete(ctx context.Context, ns *types.Namespace, c *types.Chart) (chartChanges, error) {
|
||||
if !svc.ac.CanDeleteChart(ctx, c) {
|
||||
return chartUnchanged, ChartErrNotAllowedToUndelete()
|
||||
}
|
||||
|
||||
if c.DeletedAt == nil {
|
||||
// chart not deleted
|
||||
return chartUnchanged, nil
|
||||
}
|
||||
|
||||
c.DeletedAt = nil
|
||||
return chartChanged, nil
|
||||
}
|
||||
|
||||
func loadChartCombo(ctx context.Context, s interface {
|
||||
store.ComposeCharts
|
||||
store.ComposeNamespaces
|
||||
}, namespaceID, chartID uint64) (ns *types.Namespace, c *types.Chart, err error) {
|
||||
ns, err = loadNamespace(ctx, s, namespaceID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c, err = loadChart(ctx, s, namespaceID, chartID)
|
||||
return
|
||||
}
|
||||
|
||||
func loadChart(ctx context.Context, s store.ComposeCharts, namespaceID, chartID uint64) (res *types.Chart, err error) {
|
||||
if chartID == 0 || namespaceID == 0 {
|
||||
return nil, ChartErrInvalidID()
|
||||
}
|
||||
|
||||
if res, err = store.LookupComposeChartByID(ctx, s, chartID); errors.IsNotFound(err) {
|
||||
err = ChartErrNotFound()
|
||||
}
|
||||
|
||||
if err == nil && namespaceID != res.NamespaceID {
|
||||
// Make sure chart belongs to the right namespace
|
||||
return nil, ChartErrNotFound()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// toLabeledCharts converts to []label.LabeledResource
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func toLabeledCharts(set []*types.Chart) []label.LabeledResource {
|
||||
if len(set) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ll := make([]label.LabeledResource, len(set))
|
||||
for i := range set {
|
||||
ll[i] = set[i]
|
||||
}
|
||||
|
||||
return ll
|
||||
}
|
||||
Reference in New Issue
Block a user