-
Notifications
You must be signed in to change notification settings - Fork 24
HYPERFLEET-1160 - feat: separate resource_labels table for generic resource labels #270
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| package api | ||
|
|
||
| import "fmt" | ||
|
|
||
| const ( | ||
| MaxLabelKeyLen = 255 | ||
| MaxLabelValueLen = 255 | ||
| ) | ||
|
|
||
| type ResourceLabel struct { | ||
| ResourceID string `json:"-" gorm:"primaryKey;size:255;not null"` | ||
| Key string `json:"key" gorm:"primaryKey;size:255;not null"` | ||
| Value string `json:"value" gorm:"size:255;not null"` | ||
| } | ||
|
|
||
| func (ResourceLabel) TableName() string { | ||
| return "resource_labels" | ||
| } | ||
|
|
||
| func ValidateLabel(key, value string) error { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we add a limit to how many labels can be added per resource? Consider a case of a huge bulk insert. Should that be a security concern?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My opinion is no... |
||
| if key == "" { | ||
| return fmt.Errorf("label key cannot be empty") | ||
| } | ||
| if len(key) > MaxLabelKeyLen { | ||
| return fmt.Errorf("label key %q exceeds maximum length of %d", key, MaxLabelKeyLen) | ||
| } | ||
| if len(value) > MaxLabelValueLen { | ||
| return fmt.Errorf("label value for key %q exceeds maximum length of %d", key, MaxLabelValueLen) | ||
| } | ||
| return nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| package api | ||
|
|
||
| import ( | ||
| "strings" | ||
| "testing" | ||
|
|
||
| . "github.com/onsi/gomega" | ||
| ) | ||
|
|
||
| func TestValidateLabel(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| key string | ||
| value string | ||
| expectError bool | ||
| }{ | ||
| { | ||
| name: "empty key returns error", | ||
| key: "", | ||
| value: "v", | ||
| expectError: true, | ||
| }, | ||
| { | ||
| name: "valid key and value", | ||
| key: "env", | ||
| value: "prod", | ||
| expectError: false, | ||
| }, | ||
| { | ||
| name: "key at exactly 255 chars", | ||
| key: strings.Repeat("k", MaxLabelKeyLen), | ||
| value: "v", | ||
| expectError: false, | ||
| }, | ||
| { | ||
| name: "key at 256 chars returns error", | ||
| key: strings.Repeat("k", MaxLabelKeyLen+1), | ||
| value: "v", | ||
| expectError: true, | ||
| }, | ||
| { | ||
| name: "value at exactly 255 chars", | ||
| key: "env", | ||
| value: strings.Repeat("v", MaxLabelValueLen), | ||
| expectError: false, | ||
| }, | ||
| { | ||
| name: "value at 256 chars returns error", | ||
| key: "env", | ||
| value: strings.Repeat("v", MaxLabelValueLen+1), | ||
| expectError: true, | ||
| }, | ||
| { | ||
| name: "empty value is allowed", | ||
| key: "env", | ||
| value: "", | ||
| expectError: false, | ||
| }, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| RegisterTestingT(t) | ||
|
|
||
| err := ValidateLabel(tt.key, tt.value) | ||
|
|
||
| if tt.expectError { | ||
| Expect(err).ToNot(BeNil()) | ||
| return | ||
| } | ||
|
|
||
| Expect(err).To(BeNil()) | ||
| }) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| package dao | ||
|
|
||
| import ( | ||
| "context" | ||
|
|
||
| "github.com/openshift-hyperfleet/hyperfleet-api/pkg/api" | ||
| "github.com/openshift-hyperfleet/hyperfleet-api/pkg/db" | ||
| ) | ||
|
|
||
| //go:generate mockgen-v0.6.0 -source=resource_label.go -package=dao -destination=resource_label_mock.go | ||
|
|
||
| type ResourceLabelDao interface { | ||
| ReplaceLabels(ctx context.Context, resourceID string, labels []api.ResourceLabel) error | ||
| } | ||
|
|
||
| var _ ResourceLabelDao = &sqlResourceLabelDao{} | ||
|
|
||
| type sqlResourceLabelDao struct { | ||
| sessionFactory db.SessionFactory | ||
| } | ||
|
|
||
| func NewResourceLabelDao(sessionFactory db.SessionFactory) ResourceLabelDao { | ||
| return &sqlResourceLabelDao{sessionFactory: sessionFactory} | ||
| } | ||
|
|
||
| func (d *sqlResourceLabelDao) ReplaceLabels( | ||
|
kuudori marked this conversation as resolved.
|
||
| ctx context.Context, resourceID string, labels []api.ResourceLabel, | ||
| ) error { | ||
| g2 := d.sessionFactory.New(ctx) | ||
|
|
||
| if err := g2.Where("resource_id = ?", resourceID).Delete(&api.ResourceLabel{}).Error; err != nil { | ||
| db.MarkForRollback(ctx, err) | ||
| return err | ||
| } | ||
|
|
||
| if len(labels) > 0 { | ||
| rows := make([]api.ResourceLabel, len(labels)) | ||
| copy(rows, labels) | ||
| for i := range rows { | ||
| rows[i].ResourceID = resourceID | ||
| } | ||
| if err := g2.Create(&rows).Error; err != nil { | ||
| db.MarkForRollback(ctx, err) | ||
| return err | ||
| } | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| package migrations | ||
|
|
||
| import ( | ||
| "github.com/go-gormigrate/gormigrate/v2" | ||
| "gorm.io/gorm" | ||
| ) | ||
|
|
||
| func addResourceLabels() *gormigrate.Migration { | ||
| return &gormigrate.Migration{ | ||
| ID: "202607010001", | ||
| Migrate: func(tx *gorm.DB) error { | ||
| if err := tx.Exec(`CREATE TABLE IF NOT EXISTS resource_labels ( | ||
| resource_id VARCHAR(255) NOT NULL, | ||
| key VARCHAR(255) NOT NULL, | ||
| value VARCHAR(255) NOT NULL, | ||
| PRIMARY KEY (resource_id, key), | ||
| FOREIGN KEY (resource_id) REFERENCES resources(id) ON DELETE CASCADE | ||
| );`).Error; err != nil { | ||
| return err | ||
| } | ||
|
|
||
| if err := tx.Exec(`ALTER TABLE resources DROP COLUMN IF EXISTS labels;`).Error; err != nil { | ||
| return err | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| return nil | ||
| }, | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.