diff --git a/pkg/storage/driver/sql.go b/pkg/storage/driver/sql.go index 0c3274c82..66552d03f 100644 --- a/pkg/storage/driver/sql.go +++ b/pkg/storage/driver/sql.go @@ -34,7 +34,7 @@ import ( var _ Driver = (*SQL)(nil) -var labelMap = map[string]struct{}{ +var LabelMap = map[string]struct{}{ "modifiedAt": {}, "createdAt": {}, "version": {}, @@ -48,9 +48,10 @@ const postgreSQLDialect = "postgres" // SQLDriverName is the string name of this driver. const SQLDriverName = "SQL" -const sqlReleaseTableName = "releases_v1" - const ( + sqlReleaseTableName = "releases_v1" + sqlLabelTableName = "Labels_v1" + sqlReleaseTableKeyColumn = "key" sqlReleaseTableTypeColumn = "type" sqlReleaseTableBodyColumn = "body" @@ -61,6 +62,11 @@ const ( sqlReleaseTableOwnerColumn = "owner" sqlReleaseTableCreatedAtColumn = "createdAt" sqlReleaseTableModifiedAtColumn = "modifiedAt" + + sqlLabelTableReleaseKeyColumn = "releaseKey" + sqlLabelTableReleaseNamespaceColumn = "releaseNamespace" + sqlLabelTableKeyColumn = "key" + sqlLabelTableValueColumn = "value" ) const ( @@ -151,9 +157,38 @@ func (s *SQL) ensureDBSetup() error { }, }, { - Id: "labels", - Up: []string{"CREATE TABLE labels (release_key VARCHAR(67), release_namespace VARCHAR(67), key VARCHAR(64), value VARCHAR(67));"}, - Down: []string{"DELETE TABLE labels;"}, + Id: "Labels", + Up: []string{ + fmt.Sprintf(` + CREATE TABLE %s ( + %s VARCHAR(67), + %s VARCHAR(67), + %s VARCHAR(64), + %s VARCHAR(67) + ); + CREATE INDEX ON %s (%s, %s); + + GRANT ALL ON %s TO PUBLIC; + + ALTER TABLE %s ENABLE ROW LEVEL SECURITY; + `, + sqlLabelTableName, + sqlLabelTableReleaseKeyColumn, + sqlLabelTableReleaseNamespaceColumn, + sqlLabelTableKeyColumn, + sqlLabelTableValueColumn, + sqlLabelTableName, + sqlLabelTableReleaseKeyColumn, + sqlLabelTableReleaseNamespaceColumn, + sqlLabelTableName, + sqlLabelTableName, + ), + }, + Down: []string{ + fmt.Sprintf(` + DELETE TABLE %s; + `, sqlLabelTableName), + }, }, }, } @@ -173,7 +208,7 @@ type SQLReleaseWrapper struct { // The rspb.Release body, as a base64-encoded string Body string `db:"body"` - // Release "labels" that can be used as filters in the storage.Query(labels map[string]string) + // Release "Labels" that can be used as filters in the storage.Query(Labels map[string]string) // we implemented. Note that allowing Helm users to filter against new dimensions will require a // new migration to be added, and the Create and/or update functions to be updated accordingly. Name string `db:"name"` @@ -185,6 +220,13 @@ type SQLReleaseWrapper struct { ModifiedAt int `db:"modifiedAt"` } +type SQLReleaseLabelWrapper struct { + ReleaseKey string `db:"release_key"` + ReleaseNamespace string `db:"release_namespace"` + Key string `db:"key"` + Value string `db:"value"` +} + // NewSQL initializes a new sql driver. func NewSQL(connectionString string, logger func(string, ...interface{}), namespace string) (*SQL, error) { db, err := sqlx.Connect(postgreSQLDialect, connectionString) @@ -235,6 +277,29 @@ func (s *SQL) Get(key string) (*rspb.Release, error) { return nil, err } + LabelsQuery, args, err := s.statementBuilder. + Select(sqlLabelTableKeyColumn, sqlLabelTableValueColumn). + From(sqlLabelTableName). + Where(sq.Eq{sqlLabelTableReleaseKeyColumn: key, + sqlLabelTableReleaseNamespaceColumn: s.namespace}). + ToSql() + if err != nil { + s.Log("failed to build query: %v", err) + return nil, err + } + + var LabelsList = []SQLReleaseLabelWrapper{} + if err := s.db.Select(&LabelsList, LabelsQuery, args...); err != nil { + s.Log("get: failed to get release Labels: %v", err) + return nil, err + } + + LabelsMap := make(map[string]string) + for _, i := range LabelsList { + LabelsMap[i.Key] = i.Value + } + release.Labels = filterSystemLabels(LabelsMap) + return release, nil } @@ -277,23 +342,23 @@ func (s *SQL) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) { return releases, nil } -// Query returns the set of releases that match the provided set of labels. -func (s *SQL) Query(labels map[string]string) ([]*rspb.Release, error) { +// Query returns the set of releases that match the provided set of Labels. +func (s *SQL) Query(Labels map[string]string) ([]*rspb.Release, error) { sb := s.statementBuilder. Select(sqlReleaseTableBodyColumn). From(sqlReleaseTableName) - keys := make([]string, 0, len(labels)) - for key := range labels { + keys := make([]string, 0, len(Labels)) + for key := range Labels { keys = append(keys, key) } sort.Strings(keys) for _, key := range keys { - if _, ok := labelMap[key]; ok { - sb = sb.Where(sq.Eq{key: labels[key]}) + if _, ok := LabelMap[key]; ok { + sb = sb.Where(sq.Eq{key: Labels[key]}) } else { - s.Log("unknown label %s", key) - return nil, fmt.Errorf("unknow label %s", key) + s.Log("unknown Label %s", key) + return nil, fmt.Errorf("unknow Label %s", key) } } @@ -311,7 +376,7 @@ func (s *SQL) Query(labels map[string]string) ([]*rspb.Release, error) { var records = []SQLReleaseWrapper{} if err := s.db.Select(&records, query, args...); err != nil { - s.Log("list: failed to query with labels: %v", err) + s.Log("list: failed to query with Labels: %v", err) return nil, err } @@ -407,12 +472,12 @@ func (s *SQL) Create(key string, rls *rspb.Release) error { for lk, lv := range rls.Labels { insertLabelsQuery, args, err := s.statementBuilder. - Insert("labels"). + Insert(sqlLabelTableName). Columns( - "release_key", - "release_namespace", - "key", - "value", + sqlLabelTableReleaseKeyColumn, + sqlLabelTableReleaseNamespaceColumn, + sqlLabelTableKeyColumn, + sqlLabelTableValueColumn, ). Values( key, @@ -429,7 +494,7 @@ func (s *SQL) Create(key string, rls *rspb.Release) error { if _, err := transaction.Exec(insertLabelsQuery, args...); err != nil { defer transaction.Rollback() - s.Log("failed to write labels: %v", err) + s.Log("failed to write Labels: %v", err) return err } } @@ -527,13 +592,13 @@ func (s *SQL) Delete(key string) (*rspb.Release, error) { } deleteLabelsQuery, args, err := s.statementBuilder. - Delete("labels"). - Where(sq.Eq{"release_key": key}). - Where(sq.Eq{"release_namespace": s.namespace}). + Delete(sqlLabelTableName). + Where(sq.Eq{sqlLabelTableReleaseKeyColumn: key}). + Where(sq.Eq{sqlLabelTableReleaseNamespaceColumn: s.namespace}). ToSql() if err != nil { - s.Log("failed to build delete labels query: %v", err) + s.Log("failed to build delete Labels query: %v", err) return nil, err } diff --git a/pkg/storage/driver/sql_test.go b/pkg/storage/driver/sql_test.go index 1562a90aa..e76654c17 100644 --- a/pkg/storage/driver/sql_test.go +++ b/pkg/storage/driver/sql_test.go @@ -62,6 +62,27 @@ func TestSQLGet(t *testing.T) { ), ).RowsWillBeClosed() + queryLabels := fmt.Sprintf( + regexp.QuoteMeta("SELECT %s, %s FROM %s WHERE %s = $1 AND %s = $2"), + sqlLabelTableKeyColumn, + sqlLabelTableValueColumn, + sqlLabelTableName, + sqlLabelTableReleaseKeyColumn, + sqlLabelTableReleaseNamespaceColumn, + ) + + eq := mock.ExpectQuery(queryLabels). + WithArgs(key, namespace) + + returnRows := mock.NewRows([]string{ + sqlLabelTableKeyColumn, + sqlLabelTableValueColumn, + }) + for k, v := range rel.Labels { + returnRows.AddRow(k, v) + } + eq.WillReturnRows(returnRows).RowsWillBeClosed() + got, err := sqlDriver.Get(key) if err != nil { t.Fatalf("Failed to get release: %v", err) @@ -181,6 +202,21 @@ func TestSqlCreate(t *testing.T) { ExpectExec(regexp.QuoteMeta(query)). WithArgs(key, sqlReleaseDefaultType, body, rel.Name, rel.Namespace, int(rel.Version), rel.Info.Status.String(), sqlReleaseDefaultOwner, int(time.Now().Unix())). WillReturnResult(sqlmock.NewResult(1, 1)) + + labelsQuery := fmt.Sprintf( + "INSERT INTO %s (%s,%s,%s,%s) VALUES ($1,$2,$3,$4)", + sqlLabelTableName, + sqlLabelTableReleaseKeyColumn, + sqlLabelTableReleaseNamespaceColumn, + sqlLabelTableKeyColumn, + sqlLabelTableValueColumn, + ) + for k, v := range rel.Labels { + mock. + ExpectExec(regexp.QuoteMeta(labelsQuery)). + WithArgs(key, rel.Namespace, k, v). + WillReturnResult(sqlmock.NewResult(1, 1)) + } mock.ExpectCommit() if err := sqlDriver.Create(key, rel); err != nil { @@ -421,11 +457,22 @@ func TestSqlDelete(t *testing.T) { sqlReleaseTableKeyColumn, sqlReleaseTableNamespaceColumn, ) - mock. ExpectExec(regexp.QuoteMeta(deleteQuery)). WithArgs(key, namespace). WillReturnResult(sqlmock.NewResult(0, 1)) + + deleteLabelsQuery := fmt.Sprintf( + "DELETE FROM %s WHERE %s = $1 AND %s = $2", + sqlLabelTableName, + sqlLabelTableReleaseKeyColumn, + sqlLabelTableReleaseNamespaceColumn, + ) + mock. + ExpectExec(regexp.QuoteMeta(deleteLabelsQuery)). + WithArgs(key, namespace). + WillReturnResult(sqlmock.NewResult(0, 1)) + mock.ExpectCommit() deletedRelease, err := sqlDriver.Delete(key)