From 370ee13edd09db7661cacb5f6f8f8f047c111d25 Mon Sep 17 00:00:00 2001 From: Mateusz Hawrus Date: Tue, 18 Jul 2023 18:53:59 +0200 Subject: [PATCH 1/2] merge kind definitions --- manifest/kind.go | 46 +++++ manifest/kind_enum.go | 175 ++++++++++++++++++ manifest/models.go | 4 +- manifest/v1alpha/alertsilence.go | 2 +- manifest/v1alpha/datasources.go | 9 +- manifest/v1alpha/objects.go | 109 ++++------- manifest/v1alpha/objects_test.go | 27 +-- .../test_data/conflicting_usergroup.yaml | 42 +++++ .../expected_error_conflicting_slo.txt | 1 + manifest/v1alpha/user_groups.go | 8 +- manifest/v1alpha/validator.go | 12 +- scripts/check-generate.sh | 6 +- sdk/client.go | 11 +- sdk/client_test.go | 8 +- sdk/definitions/metadata.go | 5 +- sdk/kind.go | 30 --- sdk/kind_enum.go | 118 ------------ 17 files changed, 356 insertions(+), 257 deletions(-) create mode 100644 manifest/kind.go create mode 100644 manifest/kind_enum.go create mode 100644 manifest/v1alpha/test_data/conflicting_usergroup.yaml delete mode 100644 sdk/kind.go delete mode 100644 sdk/kind_enum.go diff --git a/manifest/kind.go b/manifest/kind.go new file mode 100644 index 00000000..f9bb7260 --- /dev/null +++ b/manifest/kind.go @@ -0,0 +1,46 @@ +package manifest + +//go:generate ../bin/go-enum --nocase --lower --names --marshal --values + +import "strings" + +// Kind represents all the object kinds available in the API to perform operations on. +/* ENUM( +SLO = 1 +Service +Agent +AlertPolicy +AlertSilence +Alert +Project +AlertMethod +Direct +DataExport +RoleBinding +Annotation +UserGroup +)*/ +type Kind int + +// ToLower converts the Kind to a lower case string. +func (k Kind) ToLower() string { + return strings.ToLower(k.String()) +} + +// Applicable returns true if the Kind can be applied or deleted by the user. +// In other words, it informs whether the Kind's lifecycle is managed by the user. +func (k Kind) Applicable() bool { + return k != KindAlert +} + +// ApplicableKinds returns all the Kind instances which can be applied or deleted by the user. +func ApplicableKinds() []Kind { + allValues := KindValues() + applicable := make([]Kind, 0, len(allValues)-1) + for _, value := range allValues { + if value.Applicable() { + applicable = append(applicable, value) + } + } + return applicable +} diff --git a/manifest/kind_enum.go b/manifest/kind_enum.go new file mode 100644 index 00000000..34c01583 --- /dev/null +++ b/manifest/kind_enum.go @@ -0,0 +1,175 @@ +// Code generated by go-enum DO NOT EDIT. +// Version: 0.5.6 +// Revision: 97611fddaa414f53713597918c5e954646cb8623 +// Build Date: 2023-03-26T21:38:06Z +// Built By: goreleaser + +package manifest + +import ( + "fmt" + "strings" +) + +const ( + // KindSLO is a Kind of type SLO. + KindSLO Kind = iota + 1 + // KindService is a Kind of type Service. + KindService + // KindAgent is a Kind of type Agent. + KindAgent + // KindAlertPolicy is a Kind of type AlertPolicy. + KindAlertPolicy + // KindAlertSilence is a Kind of type AlertSilence. + KindAlertSilence + // KindAlert is a Kind of type Alert. + KindAlert + // KindProject is a Kind of type Project. + KindProject + // KindAlertMethod is a Kind of type AlertMethod. + KindAlertMethod + // KindDirect is a Kind of type Direct. + KindDirect + // KindDataExport is a Kind of type DataExport. + KindDataExport + // KindRoleBinding is a Kind of type RoleBinding. + KindRoleBinding + // KindAnnotation is a Kind of type Annotation. + KindAnnotation + // KindUserGroup is a Kind of type UserGroup. + KindUserGroup +) + +var ErrInvalidKind = fmt.Errorf("not a valid Kind, try [%s]", strings.Join(_KindNames, ", ")) + +const _KindName = "SLOServiceAgentAlertPolicyAlertSilenceAlertProjectAlertMethodDirectDataExportRoleBindingAnnotationUserGroup" + +var _KindNames = []string{ + _KindName[0:3], + _KindName[3:10], + _KindName[10:15], + _KindName[15:26], + _KindName[26:38], + _KindName[38:43], + _KindName[43:50], + _KindName[50:61], + _KindName[61:67], + _KindName[67:77], + _KindName[77:88], + _KindName[88:98], + _KindName[98:107], +} + +// KindNames returns a list of possible string values of Kind. +func KindNames() []string { + tmp := make([]string, len(_KindNames)) + copy(tmp, _KindNames) + return tmp +} + +// KindValues returns a list of the values for Kind +func KindValues() []Kind { + return []Kind{ + KindSLO, + KindService, + KindAgent, + KindAlertPolicy, + KindAlertSilence, + KindAlert, + KindProject, + KindAlertMethod, + KindDirect, + KindDataExport, + KindRoleBinding, + KindAnnotation, + KindUserGroup, + } +} + +var _KindMap = map[Kind]string{ + KindSLO: _KindName[0:3], + KindService: _KindName[3:10], + KindAgent: _KindName[10:15], + KindAlertPolicy: _KindName[15:26], + KindAlertSilence: _KindName[26:38], + KindAlert: _KindName[38:43], + KindProject: _KindName[43:50], + KindAlertMethod: _KindName[50:61], + KindDirect: _KindName[61:67], + KindDataExport: _KindName[67:77], + KindRoleBinding: _KindName[77:88], + KindAnnotation: _KindName[88:98], + KindUserGroup: _KindName[98:107], +} + +// String implements the Stringer interface. +func (x Kind) String() string { + if str, ok := _KindMap[x]; ok { + return str + } + return fmt.Sprintf("Kind(%d)", x) +} + +// IsValid provides a quick way to determine if the typed value is +// part of the allowed enumerated values +func (x Kind) IsValid() bool { + _, ok := _KindMap[x] + return ok +} + +var _KindValue = map[string]Kind{ + _KindName[0:3]: KindSLO, + strings.ToLower(_KindName[0:3]): KindSLO, + _KindName[3:10]: KindService, + strings.ToLower(_KindName[3:10]): KindService, + _KindName[10:15]: KindAgent, + strings.ToLower(_KindName[10:15]): KindAgent, + _KindName[15:26]: KindAlertPolicy, + strings.ToLower(_KindName[15:26]): KindAlertPolicy, + _KindName[26:38]: KindAlertSilence, + strings.ToLower(_KindName[26:38]): KindAlertSilence, + _KindName[38:43]: KindAlert, + strings.ToLower(_KindName[38:43]): KindAlert, + _KindName[43:50]: KindProject, + strings.ToLower(_KindName[43:50]): KindProject, + _KindName[50:61]: KindAlertMethod, + strings.ToLower(_KindName[50:61]): KindAlertMethod, + _KindName[61:67]: KindDirect, + strings.ToLower(_KindName[61:67]): KindDirect, + _KindName[67:77]: KindDataExport, + strings.ToLower(_KindName[67:77]): KindDataExport, + _KindName[77:88]: KindRoleBinding, + strings.ToLower(_KindName[77:88]): KindRoleBinding, + _KindName[88:98]: KindAnnotation, + strings.ToLower(_KindName[88:98]): KindAnnotation, + _KindName[98:107]: KindUserGroup, + strings.ToLower(_KindName[98:107]): KindUserGroup, +} + +// ParseKind attempts to convert a string to a Kind. +func ParseKind(name string) (Kind, error) { + if x, ok := _KindValue[name]; ok { + return x, nil + } + // Case insensitive parse, do a separate lookup to prevent unnecessary cost of lowercasing a string if we don't need to. + if x, ok := _KindValue[strings.ToLower(name)]; ok { + return x, nil + } + return Kind(0), fmt.Errorf("%s is %w", name, ErrInvalidKind) +} + +// MarshalText implements the text marshaller method. +func (x Kind) MarshalText() ([]byte, error) { + return []byte(x.String()), nil +} + +// UnmarshalText implements the text unmarshaller method. +func (x *Kind) UnmarshalText(text []byte) error { + name := string(text) + tmp, err := ParseKind(name) + if err != nil { + return err + } + *x = tmp + return nil +} diff --git a/manifest/models.go b/manifest/models.go index 36be1a23..4f633056 100644 --- a/manifest/models.go +++ b/manifest/models.go @@ -62,7 +62,7 @@ type RoleBindingMetadata struct { // ObjectHeader represents Header which is common for all available Objects type ObjectHeader struct { APIVersion string `json:"apiVersion" validate:"required" example:"n9/v1alpha"` - Kind string `json:"kind" validate:"required" example:"kind"` + Kind Kind `json:"kind" validate:"required" example:"kind"` MetadataHolder ObjectInternal } @@ -90,7 +90,7 @@ func JSONToGenericObjects(jsonPayload []byte) ([]ObjectGeneric, error) { // for not empty field kind returns always that is not supported for this apiVersion // so have to be validated before func UnsupportedKindErr(o ObjectGeneric) error { - if strings.TrimSpace(o.Kind) == "" { + if strings.TrimSpace(o.Kind.String()) == "" { return EnhanceError(o, errors.New("missing or empty field kind for an Object")) } return EnhanceError(o, fmt.Errorf("invalid Object kind: %s for apiVersion: %s", o.Kind, o.APIVersion)) diff --git a/manifest/v1alpha/alertsilence.go b/manifest/v1alpha/alertsilence.go index 528a7faa..f566ec23 100644 --- a/manifest/v1alpha/alertsilence.go +++ b/manifest/v1alpha/alertsilence.go @@ -11,7 +11,7 @@ import ( type AlertSilence struct { manifest.ObjectInternal APIVersion string `json:"apiVersion" validate:"required" example:"n9/v1alpha"` - Kind string `json:"kind" validate:"required" example:"kind"` + Kind manifest.Kind `json:"kind" validate:"required" example:"kind"` Metadata manifest.AlertSilenceMetadata `json:"metadata"` Spec AlertSilenceSpec `json:"spec"` Status AlertSilenceStatus `json:"status,omitempty"` diff --git a/manifest/v1alpha/datasources.go b/manifest/v1alpha/datasources.go index 03d5b029..a8ec208e 100644 --- a/manifest/v1alpha/datasources.go +++ b/manifest/v1alpha/datasources.go @@ -7,6 +7,8 @@ import ( "github.com/pkg/errors" "golang.org/x/text/cases" "golang.org/x/text/language" + + "github.com/nobl9/nobl9-go/manifest" ) type DataSourceType int @@ -335,13 +337,14 @@ var directDataRetrievalMaxDuration = map[string]HistoricalRetrievalDuration{ AppDynamics.String(): {Value: ptr(30), Unit: HRDDay}, } -func GetDataRetrievalMaxDuration(kind Kind, typeName string) (HistoricalRetrievalDuration, error) { +func GetDataRetrievalMaxDuration(kind manifest.Kind, typeName string) (HistoricalRetrievalDuration, error) { + //nolint: exhaustive switch kind { - case KindAgent: + case manifest.KindAgent: if hrd, ok := agentDataRetrievalMaxDuration[typeName]; ok { return hrd, nil } - case KindDirect: + case manifest.KindDirect: if hrd, ok := directDataRetrievalMaxDuration[typeName]; ok { return hrd, nil } diff --git a/manifest/v1alpha/objects.go b/manifest/v1alpha/objects.go index 6b2a4dd7..96e8692c 100644 --- a/manifest/v1alpha/objects.go +++ b/manifest/v1alpha/objects.go @@ -18,26 +18,6 @@ const ( // HiddenValue can be used as a value of a secret field and is ignored during saving const HiddenValue = "[hidden]" -// Kind groups Objects describing a specific type. Use this alias to increase code readability. -type Kind = string - -// Possible values of field kind for valid Objects. -const ( - KindSLO Kind = "SLO" - KindService Kind = "Service" - KindAgent Kind = "Agent" - KindProject Kind = "Project" - KindAlertPolicy Kind = "AlertPolicy" - KindAlertSilence Kind = "AlertSilence" - KindAlert Kind = "Alert" - KindAlertMethod Kind = "AlertMethod" - KindDirect Kind = "Direct" - KindDataExport Kind = "DataExport" - KindRoleBinding Kind = "RoleBinding" - KindAnnotation Kind = "Annotation" - KindUserGroup Kind = "UserGroup" -) - const DatasourceStableChannel = "stable" type AgentsSlice []Agent @@ -54,18 +34,6 @@ type RoleBindingsSlice []RoleBinding type AnnotationsSlice []Annotation type UserGroupsSlice []UserGroup -func KindFromString(kindString string) Kind { - for _, kind := range []Kind{ - KindSLO, KindService, KindAgent, KindProject, KindAlertPolicy, KindAlertSilence, KindAlert, KindAlertMethod, - KindDirect, KindDataExport, KindRoleBinding, KindAnnotation, - } { - if strings.EqualFold(kindString, kind) { - return kind - } - } - return "" -} - func (agents AgentsSlice) Clone() AgentsSlice { clone := make([]Agent, len(agents)) copy(clone, agents) @@ -441,9 +409,9 @@ type Indicator struct { } type MetricSourceSpec struct { - Project string `json:"project,omitempty" validate:"omitempty,objectName" example:"default"` - Name string `json:"name" validate:"required,objectName" example:"prometheus-source"` - Kind string `json:"kind" validate:"omitempty,metricSourceKind" example:"Agent"` + Project string `json:"project,omitempty" validate:"omitempty,objectName" example:"default"` + Name string `json:"name" validate:"required,objectName" example:"prometheus-source"` + Kind manifest.Kind `json:"kind" validate:"omitempty,metricSourceKind" example:"Agent"` } // Composite represents configuration for Composite SLO. @@ -540,8 +508,8 @@ func genericToSLO(o manifest.ObjectGeneric, v validator, onlyHeader bool) (SLO, if res.Spec.Indicator.MetricSource.Project == "" { res.Spec.Indicator.MetricSource.Project = res.Metadata.Project } - if res.Spec.Indicator.MetricSource.Kind == "" { - res.Spec.Indicator.MetricSource.Kind = KindAgent + if !res.Spec.Indicator.MetricSource.Kind.IsValid() { + res.Spec.Indicator.MetricSource.Kind = manifest.KindAgent } // we're moving towards the version where raw metrics are defined on each objective, but for now, @@ -1102,7 +1070,7 @@ func (spec AgentSpec) GetType() (DataSourceType, error) { return 0, errors.New("unknown agent type") } -// genericToDirect converts ObjectGeneric to ObjectDirect +// genericToDirect converts manifest.ObjectGeneric to Direct. func genericToDirect(o manifest.ObjectGeneric, v validator, onlyHeader bool) (Direct, error) { res := Direct{ ObjectHeader: o.ObjectHeader, @@ -1300,7 +1268,7 @@ type ServiceSpec struct { type Project struct { manifest.ObjectInternal APIVersion string `json:"apiVersion" validate:"required" example:"n9/v1alpha"` - Kind string `json:"kind" validate:"required" example:"kind"` + Kind manifest.Kind `json:"kind" validate:"required" example:"kind"` Metadata manifest.ProjectMetadata `json:"metadata"` Spec ProjectSpec `json:"spec"` } @@ -1761,7 +1729,7 @@ func genericToProject(o manifest.ObjectGeneric, v validator, onlyHeader bool) (P type RoleBinding struct { manifest.ObjectInternal APIVersion string `json:"apiVersion" validate:"required" example:"n9/v1alpha"` - Kind string `json:"kind" validate:"required" example:"kind"` + Kind manifest.Kind `json:"kind" validate:"required" example:"kind"` Metadata manifest.RoleBindingMetadata `json:"metadata"` Spec RoleBindingSpec `json:"spec"` } @@ -1820,15 +1788,15 @@ const allowedAgentsToModify = 1 func Parse(o manifest.ObjectGeneric, parsedObjects *APIObjects, onlyHeaders bool) (err error) { v := NewValidator() switch o.Kind { - case KindSLO: + case manifest.KindSLO: var slo SLO slo, err = genericToSLO(o, v, onlyHeaders) parsedObjects.SLOs = append(parsedObjects.SLOs, slo) - case KindService: + case manifest.KindService: var service Service service, err = genericToService(o, v, onlyHeaders) parsedObjects.Services = append(parsedObjects.Services, service) - case KindAgent: + case manifest.KindAgent: var agent Agent if len(parsedObjects.Agents) >= allowedAgentsToModify { err = manifest.EnhanceError(o, errors.New("only one Agent can be defined in this configuration")) @@ -1836,39 +1804,39 @@ func Parse(o manifest.ObjectGeneric, parsedObjects *APIObjects, onlyHeaders bool agent, err = genericToAgent(o, v, onlyHeaders) parsedObjects.Agents = append(parsedObjects.Agents, agent) } - case KindAlertPolicy: + case manifest.KindAlertPolicy: var alertPolicy AlertPolicy alertPolicy, err = genericToAlertPolicy(o, v, onlyHeaders) parsedObjects.AlertPolicies = append(parsedObjects.AlertPolicies, alertPolicy) - case KindAlertSilence: + case manifest.KindAlertSilence: var alertSilence AlertSilence alertSilence, err = genericToAlertSilence(o, v, onlyHeaders) parsedObjects.AlertSilences = append(parsedObjects.AlertSilences, alertSilence) - case KindAlertMethod: + case manifest.KindAlertMethod: var alertMethod AlertMethod alertMethod, err = genericToAlertMethod(o, v, onlyHeaders) parsedObjects.AlertMethods = append(parsedObjects.AlertMethods, alertMethod) - case KindDirect: + case manifest.KindDirect: var direct Direct direct, err = genericToDirect(o, v, onlyHeaders) parsedObjects.Directs = append(parsedObjects.Directs, direct) - case KindDataExport: + case manifest.KindDataExport: var dataExport DataExport dataExport, err = genericToDataExport(o, v, onlyHeaders) parsedObjects.DataExports = append(parsedObjects.DataExports, dataExport) - case KindProject: + case manifest.KindProject: var project Project project, err = genericToProject(o, v, onlyHeaders) parsedObjects.Projects = append(parsedObjects.Projects, project) - case KindRoleBinding: + case manifest.KindRoleBinding: var roleBinding RoleBinding roleBinding, err = genericToRoleBinding(o, v) parsedObjects.RoleBindings = append(parsedObjects.RoleBindings, roleBinding) - case KindAnnotation: + case manifest.KindAnnotation: var annotation Annotation annotation, err = genericToAnnotation(o, v) parsedObjects.Annotations = append(parsedObjects.Annotations, annotation) - case KindUserGroup: + case manifest.KindUserGroup: var group UserGroup group, err = genericToUserGroup(o) parsedObjects.UserGroups = append(parsedObjects.UserGroups, group) @@ -1882,37 +1850,40 @@ func Parse(o manifest.ObjectGeneric, parsedObjects *APIObjects, onlyHeaders bool // Validate performs validation of parsed APIObjects. func (o APIObjects) Validate() (err error) { var errs []error - if err = validateUniquenessConstraints(KindSLO, o.SLOs); err != nil { + if err = validateUniquenessConstraints(manifest.KindSLO, o.SLOs); err != nil { + errs = append(errs, err) + } + if err = validateUniquenessConstraints(manifest.KindService, o.Services); err != nil { errs = append(errs, err) } - if err = validateUniquenessConstraints(KindService, o.Services); err != nil { + if err = validateUniquenessConstraints(manifest.KindProject, o.Projects); err != nil { errs = append(errs, err) } - if err = validateUniquenessConstraints(KindProject, o.Projects); err != nil { + if err = validateUniquenessConstraints(manifest.KindAgent, o.Agents); err != nil { errs = append(errs, err) } - if err = validateUniquenessConstraints(KindAgent, o.Agents); err != nil { + if err = validateUniquenessConstraints(manifest.KindDirect, o.Directs); err != nil { errs = append(errs, err) } - if err = validateUniquenessConstraints(KindDirect, o.Directs); err != nil { + if err = validateUniquenessConstraints(manifest.KindAlertMethod, o.AlertMethods); err != nil { errs = append(errs, err) } - if err = validateUniquenessConstraints(KindAlertMethod, o.AlertMethods); err != nil { + if err = validateUniquenessConstraints(manifest.KindAlertPolicy, o.AlertPolicies); err != nil { errs = append(errs, err) } - if err = validateUniquenessConstraints(KindAlertPolicy, o.AlertPolicies); err != nil { + if err = validateUniquenessConstraints(manifest.KindAlertSilence, o.AlertSilences); err != nil { errs = append(errs, err) } - if err = validateUniquenessConstraints(KindAlertSilence, o.AlertSilences); err != nil { + if err = validateUniquenessConstraints(manifest.KindDataExport, o.DataExports); err != nil { errs = append(errs, err) } - if err = validateUniquenessConstraints(KindDataExport, o.DataExports); err != nil { + if err = validateUniquenessConstraints(manifest.KindRoleBinding, o.RoleBindings); err != nil { errs = append(errs, err) } - if err = validateUniquenessConstraints(KindRoleBinding, o.RoleBindings); err != nil { + if err = validateUniquenessConstraints(manifest.KindAnnotation, o.Annotations); err != nil { errs = append(errs, err) } - if err = validateUniquenessConstraints(KindAnnotation, o.Annotations); err != nil { + if err = validateUniquenessConstraints(manifest.KindUserGroup, o.UserGroups); err != nil { errs = append(errs, err) } if len(errs) > 0 { @@ -1944,7 +1915,7 @@ type uniqueIdentifiersGetter interface { // validateUniquenessConstraints finds conflicting objects in a Kind slice. // It returns an error if any conflicts were encountered. // The error informs about the cause and lists ALL conflicts. -func validateUniquenessConstraints[T uniqueIdentifiersGetter](kind Kind, slice []T) error { +func validateUniquenessConstraints[T uniqueIdentifiersGetter](kind manifest.Kind, slice []T) error { unique := make(map[string]struct{}, len(slice)) var details []string for i := range slice { @@ -1963,9 +1934,9 @@ func validateUniquenessConstraints[T uniqueIdentifiersGetter](kind Kind, slice [ } // conflictDetails creates a formatted string identifying a single conflict between two objects. -func conflictDetails(kind Kind, uid uniqueIdentifiers) string { +func conflictDetails(kind manifest.Kind, uid uniqueIdentifiers) string { switch kind { - case KindProject, KindRoleBinding: + case manifest.KindProject, manifest.KindRoleBinding, manifest.KindUserGroup: return fmt.Sprintf(`"%s"`, uid.Name) default: return fmt.Sprintf(`{"Project": "%s", "%s": "%s"}`, uid.Project, kind, uid.Name) @@ -1974,15 +1945,15 @@ func conflictDetails(kind Kind, uid uniqueIdentifiers) string { // conflictError formats an error returned for a specific Kind with all it's conflicts listed as a JSON array. // nolint: stylecheck -func conflictError(kind Kind, details []string) error { +func conflictError(kind manifest.Kind, details []string) error { return fmt.Errorf(`Constraint "%s" was violated due to the following conflicts: [%s]`, constraintDetails(kind), strings.Join(details, ", ")) } // constraintDetails creates a formatted string specifying the constraint which was broken. -func constraintDetails(kind Kind) string { +func constraintDetails(kind manifest.Kind) string { switch kind { - case KindProject, KindRoleBinding: + case manifest.KindProject, manifest.KindRoleBinding, manifest.KindUserGroup: return fmt.Sprintf(`%s.metadata.name has to be unique`, kind) default: return fmt.Sprintf(`%s.metadata.name has to be unique across a single Project`, kind) diff --git a/manifest/v1alpha/objects_test.go b/manifest/v1alpha/objects_test.go index 1c119bc9..896b8ed8 100644 --- a/manifest/v1alpha/objects_test.go +++ b/manifest/v1alpha/objects_test.go @@ -25,23 +25,14 @@ var expectedError string func TestAPIObjects_Validate(t *testing.T) { objects := APIObjects{} - // All currently supported object kinds handled by Parse method. - objectKinds := []Kind{ - KindSLO, - KindService, - KindAgent, - KindProject, - KindAlertPolicy, - KindAlertSilence, - KindAlertMethod, - KindDirect, - KindDataExport, - KindRoleBinding, - KindAnnotation, - } - for _, kind := range objectKinds { + for _, kind := range manifest.ApplicableKinds() { + require.Contains(t, + expectedError, + kind.String(), + "each applicable Kind must have a designated test file and appear in the expected error") + data, err := testData.ReadFile(path.Join(testDataDir, - fmt.Sprintf("conflicting_%s.yaml", strings.ToLower(kind)))) + fmt.Sprintf("conflicting_%s.yaml", kind.ToLower()))) require.NoError(t, err) var decodedYAML []map[string]interface{} @@ -58,7 +49,7 @@ func TestAPIObjects_Validate(t *testing.T) { for _, object := range genericObjects { // So that we can skip the Agent's constraints which allows only one to be applied (at the time being). - if object.Kind == KindAgent { + if object.Kind == manifest.KindAgent { var agent Agent agent, err = genericToAgent(object, NewValidator(), false) require.NoError(t, err) @@ -74,6 +65,6 @@ func TestAPIObjects_Validate(t *testing.T) { require.Error(t, err) // Trim any trailing newlines from the file and replace the other newlines with '; ' // just to make the test file a bit easier to read and work with. - expected := strings.Replace(strings.TrimSpace(expectedError), "\n", "; ", len(objectKinds)) + expected := strings.Replace(strings.TrimSpace(expectedError), "\n", "; ", len(manifest.KindValues())) assert.EqualError(t, err, expected) } diff --git a/manifest/v1alpha/test_data/conflicting_usergroup.yaml b/manifest/v1alpha/test_data/conflicting_usergroup.yaml new file mode 100644 index 00000000..094689f4 --- /dev/null +++ b/manifest/v1alpha/test_data/conflicting_usergroup.yaml @@ -0,0 +1,42 @@ +- apiVersion: n9/v1alpha + kind: UserGroup + metadata: + name: single-conflict + spec: + displayName: yellow Practical Steel Watch + members: [] +- apiVersion: n9/v1alpha + kind: UserGroup + metadata: + name: single-conflict + spec: + displayName: violet Durable Iron Computer + members: [] +- apiVersion: n9/v1alpha + kind: UserGroup + metadata: + name: all-good + spec: + displayName: pink Small Concrete Car + members: [] +- apiVersion: n9/v1alpha + kind: UserGroup + metadata: + name: double-conflict + spec: + displayName: something + members: [] +- apiVersion: n9/v1alpha + kind: UserGroup + metadata: + name: double-conflict + spec: + displayName: different + members: [] +- apiVersion: n9/v1alpha + kind: UserGroup + metadata: + name: double-conflict + spec: + displayName: each time + members: [] diff --git a/manifest/v1alpha/test_data/expected_error_conflicting_slo.txt b/manifest/v1alpha/test_data/expected_error_conflicting_slo.txt index 4db78fef..7a519f98 100644 --- a/manifest/v1alpha/test_data/expected_error_conflicting_slo.txt +++ b/manifest/v1alpha/test_data/expected_error_conflicting_slo.txt @@ -9,3 +9,4 @@ Constraint "AlertSilence.metadata.name has to be unique across a single Project" Constraint "DataExport.metadata.name has to be unique across a single Project" was violated due to the following conflicts: [{"Project": "default", "DataExport": "single-conflict"}, {"Project": "non-default", "DataExport": "double-conflict"}, {"Project": "non-default", "DataExport": "double-conflict"}] Constraint "RoleBinding.metadata.name has to be unique" was violated due to the following conflicts: ["single-conflict", "double-conflict", "double-conflict"] Constraint "Annotation.metadata.name has to be unique across a single Project" was violated due to the following conflicts: [{"Project": "default", "Annotation": "single-conflict"}, {"Project": "super-project", "Annotation": "double-conflict"}, {"Project": "super-project", "Annotation": "double-conflict"}] +Constraint "UserGroup.metadata.name has to be unique" was violated due to the following conflicts: ["single-conflict", "double-conflict", "double-conflict"] diff --git a/manifest/v1alpha/user_groups.go b/manifest/v1alpha/user_groups.go index 1488333b..37930c64 100644 --- a/manifest/v1alpha/user_groups.go +++ b/manifest/v1alpha/user_groups.go @@ -9,11 +9,17 @@ import ( type UserGroup struct { manifest.ObjectInternal APIVersion string `json:"apiVersion" validate:"required" example:"n9/v1alpha"` - Kind string `json:"kind" validate:"required" example:"kind"` + Kind manifest.Kind `json:"kind" validate:"required" example:"kind"` Metadata GroupMetadata `json:"metadata"` Spec UserGroupSpec `json:"spec"` } +// getUniqueIdentifiers returns uniqueIdentifiers used to check +// potential conflicts between simultaneously applied objects. +func (u UserGroup) getUniqueIdentifiers() uniqueIdentifiers { + return uniqueIdentifiers{Name: u.Metadata.Name} +} + // UserGroupSpec represents content of UserGroup's Spec type UserGroupSpec struct { DisplayName string `json:"displayName"` diff --git a/manifest/v1alpha/validator.go b/manifest/v1alpha/validator.go index 3c400a84..3f2b881b 100644 --- a/manifest/v1alpha/validator.go +++ b/manifest/v1alpha/validator.go @@ -2314,8 +2314,16 @@ func isValidRoleARN(fl v.FieldLevel) bool { } func isValidMetricSourceKind(fl v.FieldLevel) bool { - value := fl.Field().String() - return value == KindAgent || value == KindDirect + switch fl.Field().Kind() { + case reflect.Int: + kind := manifest.Kind(fl.Field().Int()) + if !kind.IsValid() { + return false + } + return kind == manifest.KindAgent || kind == manifest.KindDirect + default: + return false + } } func isValidMetricPathGraphite(fl v.FieldLevel) bool { diff --git a/scripts/check-generate.sh b/scripts/check-generate.sh index 67be4e8e..5b05a2cb 100755 --- a/scripts/check-generate.sh +++ b/scripts/check-generate.sh @@ -4,12 +4,12 @@ set -e make generate -ENUM_PATH=sdk/kind_enum.go +ENUM_PATH="*_enum.go" -CHANGED=$(git diff --name-only ${ENUM_PATH}) +CHANGED=$(git diff --name-only "${ENUM_PATH}") if [ -n "${CHANGED}" ]; then echo >&2 "There are generated code changes that haven't been committed: ${CHANGED}" - git restore ${ENUM_PATH} + git restore "${ENUM_PATH}" exit 1 else echo "Looks good!" diff --git a/sdk/client.go b/sdk/client.go index ba3d6ddf..0b03c031 100644 --- a/sdk/client.go +++ b/sdk/client.go @@ -17,6 +17,7 @@ import ( "github.com/pkg/errors" + "github.com/nobl9/nobl9-go/manifest" "github.com/nobl9/nobl9-go/sdk/retryhttp" ) @@ -179,7 +180,7 @@ const ( func (c *Client) GetObjects( ctx context.Context, project string, - kind Kind, + kind manifest.Kind, filterLabel map[string][]string, names ...string, ) ([]AnyJSONObj, error) { @@ -200,7 +201,7 @@ func (c *Client) GetObjects( func (c *Client) GetObjectsWithParams( ctx context.Context, project string, - kind Kind, + kind manifest.Kind, q url.Values, ) (response Response, err error) { response = Response{TruncatedMax: -1} @@ -250,9 +251,9 @@ func (c *Client) GetObjectsWithParams( } } -func (c *Client) resolveGetObjectEndpoint(kind Kind) string { +func (c *Client) resolveGetObjectEndpoint(kind manifest.Kind) string { switch kind { - case KindUserGroup: + case manifest.KindUserGroup: return apiGetGroups default: return path.Join(apiGet, kind.ToLower()) @@ -365,7 +366,7 @@ func (c *Client) GetAWSExternalID(ctx context.Context, project string) (string, func (c *Client) DeleteObjectsByName( ctx context.Context, project string, - kind Kind, + kind manifest.Kind, dryRun bool, names ...string, ) error { diff --git a/sdk/client_test.go b/sdk/client_test.go index ee1024dd..f0771fde 100644 --- a/sdk/client_test.go +++ b/sdk/client_test.go @@ -16,6 +16,8 @@ import ( "github.com/lestrrat-go/jwx/jwk" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/nobl9/nobl9-go/manifest" ) func TestClient_GetObjects(t *testing.T) { @@ -63,7 +65,7 @@ func TestClient_GetObjects(t *testing.T) { objects, err := client.GetObjects( context.Background(), "non-default", - KindService, + manifest.KindService, map[string][]string{"team": {"green", "purple"}}, "service1", "service2", ) @@ -92,7 +94,7 @@ func TestClient_GetObjects_GroupsEndpoint(t *testing.T) { defer srv.Close() // Run the API method. - _, err := client.GetObjects(context.Background(), "", KindUserGroup, nil) + _, err := client.GetObjects(context.Background(), "", manifest.KindUserGroup, nil) // Verify response handling. require.NoError(t, err) assert.Equal(t, 1, calledTimes) @@ -199,7 +201,7 @@ func TestClient_DeleteObjectsByName(t *testing.T) { err := client.DeleteObjectsByName( context.Background(), "my-project", - KindService, + manifest.KindService, true, "service1", "service2", diff --git a/sdk/definitions/metadata.go b/sdk/definitions/metadata.go index 2e8225b0..637fe260 100644 --- a/sdk/definitions/metadata.go +++ b/sdk/definitions/metadata.go @@ -3,6 +3,7 @@ package definitions import ( "fmt" + "github.com/nobl9/nobl9-go/manifest" "github.com/nobl9/nobl9-go/sdk" ) @@ -35,12 +36,12 @@ func (ma MetadataAnnotations) AnnotateObject(object sdk.AnyJSONObj) (sdk.AnyJSON if !ok { return nil, fmt.Errorf("cannot retrieve object kind") } - kind, err := sdk.ParseKind(kindStr) + kind, err := manifest.ParseKind(kindStr) if err != nil { return nil, err } switch kind { - case sdk.KindProject, sdk.KindRoleBinding, sdk.KindUserGroup: + case manifest.KindProject, manifest.KindRoleBinding, manifest.KindUserGroup: // Do not append the project name. default: if meta["project"] == nil && ma.Project != "" { diff --git a/sdk/kind.go b/sdk/kind.go deleted file mode 100644 index 995e9fc4..00000000 --- a/sdk/kind.go +++ /dev/null @@ -1,30 +0,0 @@ -package sdk - -//go:generate ../bin/go-enum --nocomments --nocase - -import "strings" - -// Kind represents all the object kinds available in the API to perform operations on. -/* ENUM( -SLO -Service -Agent -AlertPolicy -AlertSilence -Alert -Project -AlertMethod -MetricSource -Direct -DataExport -UsageSummary -RoleBinding -SLOErrorBudgetStatus -Annotation -UserGroup -)*/ -type Kind int - -func (k Kind) ToLower() string { - return strings.ToLower(k.String()) -} diff --git a/sdk/kind_enum.go b/sdk/kind_enum.go deleted file mode 100644 index 8bbf7a35..00000000 --- a/sdk/kind_enum.go +++ /dev/null @@ -1,118 +0,0 @@ -// Code generated by go-enum DO NOT EDIT. -// Version: 0.5.6 -// Revision: 97611fddaa414f53713597918c5e954646cb8623 -// Build Date: 2023-03-26T21:38:06Z -// Built By: goreleaser - -package sdk - -import ( - "fmt" - "strings" - - "github.com/pkg/errors" -) - -const ( - KindSLO Kind = iota - KindService - KindAgent - KindAlertPolicy - KindAlertSilence - KindAlert - KindProject - KindAlertMethod - KindMetricSource - KindDirect - KindDataExport - KindUsageSummary - KindRoleBinding - KindSLOErrorBudgetStatus - KindAnnotation - KindUserGroup -) - -var ErrInvalidKind = errors.New("not a valid Kind") - -const _KindName = "SLOServiceAgentAlertPolicyAlertSilenceAlertProjectAlertMethodMetricSourceDirectDataExportUsageSummaryRoleBindingSLOErrorBudgetStatusAnnotationUserGroup" - -var _KindMap = map[Kind]string{ - KindSLO: _KindName[0:3], - KindService: _KindName[3:10], - KindAgent: _KindName[10:15], - KindAlertPolicy: _KindName[15:26], - KindAlertSilence: _KindName[26:38], - KindAlert: _KindName[38:43], - KindProject: _KindName[43:50], - KindAlertMethod: _KindName[50:61], - KindMetricSource: _KindName[61:73], - KindDirect: _KindName[73:79], - KindDataExport: _KindName[79:89], - KindUsageSummary: _KindName[89:101], - KindRoleBinding: _KindName[101:112], - KindSLOErrorBudgetStatus: _KindName[112:132], - KindAnnotation: _KindName[132:142], - KindUserGroup: _KindName[142:151], -} - -// String implements the Stringer interface. -func (x Kind) String() string { - if str, ok := _KindMap[x]; ok { - return str - } - return fmt.Sprintf("Kind(%d)", x) -} - -// IsValid provides a quick way to determine if the typed value is -// part of the allowed enumerated values -func (x Kind) IsValid() bool { - _, ok := _KindMap[x] - return ok -} - -var _KindValue = map[string]Kind{ - _KindName[0:3]: KindSLO, - strings.ToLower(_KindName[0:3]): KindSLO, - _KindName[3:10]: KindService, - strings.ToLower(_KindName[3:10]): KindService, - _KindName[10:15]: KindAgent, - strings.ToLower(_KindName[10:15]): KindAgent, - _KindName[15:26]: KindAlertPolicy, - strings.ToLower(_KindName[15:26]): KindAlertPolicy, - _KindName[26:38]: KindAlertSilence, - strings.ToLower(_KindName[26:38]): KindAlertSilence, - _KindName[38:43]: KindAlert, - strings.ToLower(_KindName[38:43]): KindAlert, - _KindName[43:50]: KindProject, - strings.ToLower(_KindName[43:50]): KindProject, - _KindName[50:61]: KindAlertMethod, - strings.ToLower(_KindName[50:61]): KindAlertMethod, - _KindName[61:73]: KindMetricSource, - strings.ToLower(_KindName[61:73]): KindMetricSource, - _KindName[73:79]: KindDirect, - strings.ToLower(_KindName[73:79]): KindDirect, - _KindName[79:89]: KindDataExport, - strings.ToLower(_KindName[79:89]): KindDataExport, - _KindName[89:101]: KindUsageSummary, - strings.ToLower(_KindName[89:101]): KindUsageSummary, - _KindName[101:112]: KindRoleBinding, - strings.ToLower(_KindName[101:112]): KindRoleBinding, - _KindName[112:132]: KindSLOErrorBudgetStatus, - strings.ToLower(_KindName[112:132]): KindSLOErrorBudgetStatus, - _KindName[132:142]: KindAnnotation, - strings.ToLower(_KindName[132:142]): KindAnnotation, - _KindName[142:151]: KindUserGroup, - strings.ToLower(_KindName[142:151]): KindUserGroup, -} - -// ParseKind attempts to convert a string to a Kind. -func ParseKind(name string) (Kind, error) { - if x, ok := _KindValue[name]; ok { - return x, nil - } - // Case insensitive parse, do a separate lookup to prevent unnecessary cost of lowercasing a string if we don't need to. - if x, ok := _KindValue[strings.ToLower(name)]; ok { - return x, nil - } - return Kind(0), fmt.Errorf("%s is %w", name, ErrInvalidKind) -} From a7391c5d3c7b5454599ccac08e0f16ec16284bd9 Mon Sep 17 00:00:00 2001 From: Mateusz Hawrus Date: Wed, 19 Jul 2023 12:16:36 +0200 Subject: [PATCH 2/2] add Equals method --- manifest/kind.go | 6 ++++++ manifest/v1alpha/sli_analysis.go | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/manifest/kind.go b/manifest/kind.go index f9bb7260..0d0e61d1 100644 --- a/manifest/kind.go +++ b/manifest/kind.go @@ -27,6 +27,12 @@ func (k Kind) ToLower() string { return strings.ToLower(k.String()) } +// Equals returns true if the Kind is equal to the given string. +// The comparison is case-insensitive. +func (k Kind) Equals(s string) bool { + return strings.EqualFold(k.String(), s) +} + // Applicable returns true if the Kind can be applied or deleted by the user. // In other words, it informs whether the Kind's lifecycle is managed by the user. func (k Kind) Applicable() bool { diff --git a/manifest/v1alpha/sli_analysis.go b/manifest/v1alpha/sli_analysis.go index 760aca24..4acce57c 100644 --- a/manifest/v1alpha/sli_analysis.go +++ b/manifest/v1alpha/sli_analysis.go @@ -3,6 +3,7 @@ package v1alpha import ( "time" + "github.com/nobl9/nobl9-go/manifest" "github.com/nobl9/nobl9-go/manifest/v1alpha/twindow" ) @@ -17,7 +18,7 @@ type SLIAnalysis struct { } type AnalysisMetricSpec struct { - Kind string `json:"kind" validate:"required,metricSourceKind"` + Kind manifest.Kind `json:"kind" validate:"required,metricSourceKind"` MetricSource string `json:"metricSource" validate:"required,objectName"` RawMetric *MetricSpec `json:"rawMetric,omitempty"` CountMetrics *CountMetricsSpec `json:"countMetrics,omitempty"`