mirror of https://github.com/helm/helm
commit
b99d493a9e
@ -1,2 +1,6 @@
|
||||
scope: ahab
|
||||
name: ahab
|
||||
boat: true
|
||||
nested:
|
||||
foo: false
|
||||
bar: true
|
||||
|
@ -0,0 +1,492 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package driver // import "helm.sh/helm/v3/pkg/storage/driver"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
migrate "github.com/rubenv/sql-migrate"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
|
||||
// Import pq for postgres dialect
|
||||
_ "github.com/lib/pq"
|
||||
|
||||
rspb "helm.sh/helm/v3/pkg/release"
|
||||
)
|
||||
|
||||
var _ Driver = (*SQL)(nil)
|
||||
|
||||
var labelMap = map[string]struct{}{
|
||||
"modifiedAt": {},
|
||||
"createdAt": {},
|
||||
"version": {},
|
||||
"status": {},
|
||||
"owner": {},
|
||||
"name": {},
|
||||
}
|
||||
|
||||
const postgreSQLDialect = "postgres"
|
||||
|
||||
// SQLDriverName is the string name of this driver.
|
||||
const SQLDriverName = "SQL"
|
||||
|
||||
const sqlReleaseTableName = "releases_v1"
|
||||
|
||||
const (
|
||||
sqlReleaseTableKeyColumn = "key"
|
||||
sqlReleaseTableTypeColumn = "type"
|
||||
sqlReleaseTableBodyColumn = "body"
|
||||
sqlReleaseTableNameColumn = "name"
|
||||
sqlReleaseTableNamespaceColumn = "namespace"
|
||||
sqlReleaseTableVersionColumn = "version"
|
||||
sqlReleaseTableStatusColumn = "status"
|
||||
sqlReleaseTableOwnerColumn = "owner"
|
||||
sqlReleaseTableCreatedAtColumn = "createdAt"
|
||||
sqlReleaseTableModifiedAtColumn = "modifiedAt"
|
||||
)
|
||||
|
||||
const (
|
||||
sqlReleaseDefaultOwner = "helm"
|
||||
sqlReleaseDefaultType = "helm.sh/release.v1"
|
||||
)
|
||||
|
||||
// SQL is the sql storage driver implementation.
|
||||
type SQL struct {
|
||||
db *sqlx.DB
|
||||
namespace string
|
||||
statementBuilder sq.StatementBuilderType
|
||||
|
||||
Log func(string, ...interface{})
|
||||
}
|
||||
|
||||
// Name returns the name of the driver.
|
||||
func (s *SQL) Name() string {
|
||||
return SQLDriverName
|
||||
}
|
||||
|
||||
func (s *SQL) ensureDBSetup() error {
|
||||
// Populate the database with the relations we need if they don't exist yet
|
||||
migrations := &migrate.MemoryMigrationSource{
|
||||
Migrations: []*migrate.Migration{
|
||||
{
|
||||
Id: "init",
|
||||
Up: []string{
|
||||
fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
%s VARCHAR(67),
|
||||
%s VARCHAR(64) NOT NULL,
|
||||
%s TEXT NOT NULL,
|
||||
%s VARCHAR(64) NOT NULL,
|
||||
%s VARCHAR(64) NOT NULL,
|
||||
%s INTEGER NOT NULL,
|
||||
%s TEXT NOT NULL,
|
||||
%s TEXT NOT NULL,
|
||||
%s INTEGER NOT NULL,
|
||||
%s INTEGER NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(%s, %s)
|
||||
);
|
||||
CREATE INDEX ON %s (%s, %s);
|
||||
CREATE INDEX ON %s (%s);
|
||||
CREATE INDEX ON %s (%s);
|
||||
CREATE INDEX ON %s (%s);
|
||||
CREATE INDEX ON %s (%s);
|
||||
CREATE INDEX ON %s (%s);
|
||||
|
||||
GRANT ALL ON %s TO PUBLIC;
|
||||
|
||||
ALTER TABLE %s ENABLE ROW LEVEL SECURITY;
|
||||
`,
|
||||
sqlReleaseTableName,
|
||||
sqlReleaseTableKeyColumn,
|
||||
sqlReleaseTableTypeColumn,
|
||||
sqlReleaseTableBodyColumn,
|
||||
sqlReleaseTableNameColumn,
|
||||
sqlReleaseTableNamespaceColumn,
|
||||
sqlReleaseTableVersionColumn,
|
||||
sqlReleaseTableStatusColumn,
|
||||
sqlReleaseTableOwnerColumn,
|
||||
sqlReleaseTableCreatedAtColumn,
|
||||
sqlReleaseTableModifiedAtColumn,
|
||||
sqlReleaseTableKeyColumn,
|
||||
sqlReleaseTableNamespaceColumn,
|
||||
sqlReleaseTableName,
|
||||
sqlReleaseTableKeyColumn,
|
||||
sqlReleaseTableNamespaceColumn,
|
||||
sqlReleaseTableName,
|
||||
sqlReleaseTableVersionColumn,
|
||||
sqlReleaseTableName,
|
||||
sqlReleaseTableStatusColumn,
|
||||
sqlReleaseTableName,
|
||||
sqlReleaseTableOwnerColumn,
|
||||
sqlReleaseTableName,
|
||||
sqlReleaseTableCreatedAtColumn,
|
||||
sqlReleaseTableName,
|
||||
sqlReleaseTableModifiedAtColumn,
|
||||
sqlReleaseTableName,
|
||||
sqlReleaseTableName,
|
||||
),
|
||||
},
|
||||
Down: []string{
|
||||
fmt.Sprintf(`
|
||||
DROP TABLE %s;
|
||||
`, sqlReleaseTableName),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := migrate.Exec(s.db.DB, postgreSQLDialect, migrations, migrate.Up)
|
||||
return err
|
||||
}
|
||||
|
||||
// SQLReleaseWrapper describes how Helm releases are stored in an SQL database
|
||||
type SQLReleaseWrapper struct {
|
||||
// The primary key, made of {release-name}.{release-version}
|
||||
Key string `db:"key"`
|
||||
|
||||
// See https://github.com/helm/helm/blob/master/pkg/storage/driver/secrets.go#L236
|
||||
Type string `db:"type"`
|
||||
|
||||
// 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)
|
||||
// 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"`
|
||||
Namespace string `db:"namespace"`
|
||||
Version int `db:"version"`
|
||||
Status string `db:"status"`
|
||||
Owner string `db:"owner"`
|
||||
CreatedAt int `db:"createdAt"`
|
||||
ModifiedAt int `db:"modifiedAt"`
|
||||
}
|
||||
|
||||
// NewSQL initializes a new sql driver.
|
||||
func NewSQL(connectionString string, logger func(string, ...interface{}), namespace string) (*SQL, error) {
|
||||
db, err := sqlx.Connect(postgreSQLDialect, connectionString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
driver := &SQL{
|
||||
db: db,
|
||||
Log: logger,
|
||||
statementBuilder: sq.StatementBuilder.PlaceholderFormat(sq.Dollar),
|
||||
}
|
||||
|
||||
if err := driver.ensureDBSetup(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
driver.namespace = namespace
|
||||
|
||||
return driver, nil
|
||||
}
|
||||
|
||||
// Get returns the release named by key.
|
||||
func (s *SQL) Get(key string) (*rspb.Release, error) {
|
||||
var record SQLReleaseWrapper
|
||||
|
||||
qb := s.statementBuilder.
|
||||
Select(sqlReleaseTableBodyColumn).
|
||||
From(sqlReleaseTableName).
|
||||
Where(sq.Eq{sqlReleaseTableKeyColumn: key}).
|
||||
Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace})
|
||||
|
||||
query, args, err := qb.ToSql()
|
||||
if err != nil {
|
||||
s.Log("failed to build query: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get will return an error if the result is empty
|
||||
if err := s.db.Get(&record, query, args...); err != nil {
|
||||
s.Log("got SQL error when getting release %s: %v", key, err)
|
||||
return nil, ErrReleaseNotFound
|
||||
}
|
||||
|
||||
release, err := decodeRelease(record.Body)
|
||||
if err != nil {
|
||||
s.Log("get: failed to decode data %q: %v", key, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return release, nil
|
||||
}
|
||||
|
||||
// List returns the list of all releases such that filter(release) == true
|
||||
func (s *SQL) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) {
|
||||
sb := s.statementBuilder.
|
||||
Select(sqlReleaseTableBodyColumn).
|
||||
From(sqlReleaseTableName).
|
||||
Where(sq.Eq{sqlReleaseTableOwnerColumn: sqlReleaseDefaultOwner})
|
||||
|
||||
// If a namespace was specified, we only list releases from that namespace
|
||||
if s.namespace != "" {
|
||||
sb = sb.Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace})
|
||||
}
|
||||
|
||||
query, args, err := sb.ToSql()
|
||||
if err != nil {
|
||||
s.Log("failed to build query: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var records = []SQLReleaseWrapper{}
|
||||
if err := s.db.Select(&records, query, args...); err != nil {
|
||||
s.Log("list: failed to list: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var releases []*rspb.Release
|
||||
for _, record := range records {
|
||||
release, err := decodeRelease(record.Body)
|
||||
if err != nil {
|
||||
s.Log("list: failed to decode release: %v: %v", record, err)
|
||||
continue
|
||||
}
|
||||
if filter(release) {
|
||||
releases = append(releases, release)
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
sb := s.statementBuilder.
|
||||
Select(sqlReleaseTableBodyColumn).
|
||||
From(sqlReleaseTableName)
|
||||
|
||||
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]})
|
||||
} else {
|
||||
s.Log("unknown label %s", key)
|
||||
return nil, fmt.Errorf("unknow label %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
// If a namespace was specified, we only list releases from that namespace
|
||||
if s.namespace != "" {
|
||||
sb = sb.Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace})
|
||||
}
|
||||
|
||||
// Build our query
|
||||
query, args, err := sb.ToSql()
|
||||
if err != nil {
|
||||
s.Log("failed to build query: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var records = []SQLReleaseWrapper{}
|
||||
if err := s.db.Select(&records, query, args...); err != nil {
|
||||
s.Log("list: failed to query with labels: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var releases []*rspb.Release
|
||||
for _, record := range records {
|
||||
release, err := decodeRelease(record.Body)
|
||||
if err != nil {
|
||||
s.Log("list: failed to decode release: %v: %v", record, err)
|
||||
continue
|
||||
}
|
||||
releases = append(releases, release)
|
||||
}
|
||||
|
||||
if len(releases) == 0 {
|
||||
return nil, ErrReleaseNotFound
|
||||
}
|
||||
|
||||
return releases, nil
|
||||
}
|
||||
|
||||
// Create creates a new release.
|
||||
func (s *SQL) Create(key string, rls *rspb.Release) error {
|
||||
namespace := rls.Namespace
|
||||
if namespace == "" {
|
||||
namespace = defaultNamespace
|
||||
}
|
||||
s.namespace = namespace
|
||||
|
||||
body, err := encodeRelease(rls)
|
||||
if err != nil {
|
||||
s.Log("failed to encode release: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
transaction, err := s.db.Beginx()
|
||||
if err != nil {
|
||||
s.Log("failed to start SQL transaction: %v", err)
|
||||
return fmt.Errorf("error beginning transaction: %v", err)
|
||||
}
|
||||
|
||||
insertQuery, args, err := s.statementBuilder.
|
||||
Insert(sqlReleaseTableName).
|
||||
Columns(
|
||||
sqlReleaseTableKeyColumn,
|
||||
sqlReleaseTableTypeColumn,
|
||||
sqlReleaseTableBodyColumn,
|
||||
sqlReleaseTableNameColumn,
|
||||
sqlReleaseTableNamespaceColumn,
|
||||
sqlReleaseTableVersionColumn,
|
||||
sqlReleaseTableStatusColumn,
|
||||
sqlReleaseTableOwnerColumn,
|
||||
sqlReleaseTableCreatedAtColumn,
|
||||
).
|
||||
Values(
|
||||
key,
|
||||
sqlReleaseDefaultType,
|
||||
body,
|
||||
rls.Name,
|
||||
namespace,
|
||||
int(rls.Version),
|
||||
rls.Info.Status.String(),
|
||||
sqlReleaseDefaultOwner,
|
||||
int(time.Now().Unix()),
|
||||
).ToSql()
|
||||
if err != nil {
|
||||
s.Log("failed to build insert query: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := transaction.Exec(insertQuery, args...); err != nil {
|
||||
defer transaction.Rollback()
|
||||
|
||||
selectQuery, args, buildErr := s.statementBuilder.
|
||||
Select(sqlReleaseTableKeyColumn).
|
||||
From(sqlReleaseTableName).
|
||||
Where(sq.Eq{sqlReleaseTableKeyColumn: key}).
|
||||
Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}).
|
||||
ToSql()
|
||||
if buildErr != nil {
|
||||
s.Log("failed to build select query: %v", buildErr)
|
||||
return err
|
||||
}
|
||||
|
||||
var record SQLReleaseWrapper
|
||||
if err := transaction.Get(&record, selectQuery, args...); err == nil {
|
||||
s.Log("release %s already exists", key)
|
||||
return ErrReleaseExists
|
||||
}
|
||||
|
||||
s.Log("failed to store release %s in SQL database: %v", key, err)
|
||||
return err
|
||||
}
|
||||
defer transaction.Commit()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update updates a release.
|
||||
func (s *SQL) Update(key string, rls *rspb.Release) error {
|
||||
namespace := rls.Namespace
|
||||
if namespace == "" {
|
||||
namespace = defaultNamespace
|
||||
}
|
||||
s.namespace = namespace
|
||||
|
||||
body, err := encodeRelease(rls)
|
||||
if err != nil {
|
||||
s.Log("failed to encode release: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
query, args, err := s.statementBuilder.
|
||||
Update(sqlReleaseTableName).
|
||||
Set(sqlReleaseTableBodyColumn, body).
|
||||
Set(sqlReleaseTableNameColumn, rls.Name).
|
||||
Set(sqlReleaseTableVersionColumn, int(rls.Version)).
|
||||
Set(sqlReleaseTableStatusColumn, rls.Info.Status.String()).
|
||||
Set(sqlReleaseTableOwnerColumn, sqlReleaseDefaultOwner).
|
||||
Set(sqlReleaseTableModifiedAtColumn, int(time.Now().Unix())).
|
||||
Where(sq.Eq{sqlReleaseTableKeyColumn: key}).
|
||||
Where(sq.Eq{sqlReleaseTableNamespaceColumn: namespace}).
|
||||
ToSql()
|
||||
|
||||
if err != nil {
|
||||
s.Log("failed to build update query: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := s.db.Exec(query, args...); err != nil {
|
||||
s.Log("failed to update release %s in SQL database: %v", key, err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes a release or returns ErrReleaseNotFound.
|
||||
func (s *SQL) Delete(key string) (*rspb.Release, error) {
|
||||
transaction, err := s.db.Beginx()
|
||||
if err != nil {
|
||||
s.Log("failed to start SQL transaction: %v", err)
|
||||
return nil, fmt.Errorf("error beginning transaction: %v", err)
|
||||
}
|
||||
|
||||
selectQuery, args, err := s.statementBuilder.
|
||||
Select(sqlReleaseTableBodyColumn).
|
||||
From(sqlReleaseTableName).
|
||||
Where(sq.Eq{sqlReleaseTableKeyColumn: key}).
|
||||
Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}).
|
||||
ToSql()
|
||||
if err != nil {
|
||||
s.Log("failed to build select query: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var record SQLReleaseWrapper
|
||||
err = transaction.Get(&record, selectQuery, args...)
|
||||
if err != nil {
|
||||
s.Log("release %s not found: %v", key, err)
|
||||
return nil, ErrReleaseNotFound
|
||||
}
|
||||
|
||||
release, err := decodeRelease(record.Body)
|
||||
if err != nil {
|
||||
s.Log("failed to decode release %s: %v", key, err)
|
||||
transaction.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
defer transaction.Commit()
|
||||
|
||||
deleteQuery, args, err := s.statementBuilder.
|
||||
Delete(sqlReleaseTableName).
|
||||
Where(sq.Eq{sqlReleaseTableKeyColumn: key}).
|
||||
Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}).
|
||||
ToSql()
|
||||
if err != nil {
|
||||
s.Log("failed to build select query: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = transaction.Exec(deleteQuery, args...)
|
||||
return release, err
|
||||
}
|
@ -0,0 +1,442 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
sqlmock "github.com/DATA-DOG/go-sqlmock"
|
||||
|
||||
rspb "helm.sh/helm/v3/pkg/release"
|
||||
)
|
||||
|
||||
func TestSQLName(t *testing.T) {
|
||||
sqlDriver, _ := newTestFixtureSQL(t)
|
||||
if sqlDriver.Name() != SQLDriverName {
|
||||
t.Errorf("Expected name to be %s, got %s", SQLDriverName, sqlDriver.Name())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSQLGet(t *testing.T) {
|
||||
vers := int(1)
|
||||
name := "smug-pigeon"
|
||||
namespace := "default"
|
||||
key := testKey(name, vers)
|
||||
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
|
||||
|
||||
body, _ := encodeRelease(rel)
|
||||
|
||||
sqlDriver, mock := newTestFixtureSQL(t)
|
||||
|
||||
query := fmt.Sprintf(
|
||||
regexp.QuoteMeta("SELECT %s FROM %s WHERE %s = $1 AND %s = $2"),
|
||||
sqlReleaseTableBodyColumn,
|
||||
sqlReleaseTableName,
|
||||
sqlReleaseTableKeyColumn,
|
||||
sqlReleaseTableNamespaceColumn,
|
||||
)
|
||||
|
||||
mock.
|
||||
ExpectQuery(query).
|
||||
WithArgs(key, namespace).
|
||||
WillReturnRows(
|
||||
mock.NewRows([]string{
|
||||
sqlReleaseTableBodyColumn,
|
||||
}).AddRow(
|
||||
body,
|
||||
),
|
||||
).RowsWillBeClosed()
|
||||
|
||||
got, err := sqlDriver.Get(key)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get release: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(rel, got) {
|
||||
t.Errorf("Expected release {%v}, got {%v}", rel, got)
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("sql expectations weren't met: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSQLList(t *testing.T) {
|
||||
body1, _ := encodeRelease(releaseStub("key-1", 1, "default", rspb.StatusUninstalled))
|
||||
body2, _ := encodeRelease(releaseStub("key-2", 1, "default", rspb.StatusUninstalled))
|
||||
body3, _ := encodeRelease(releaseStub("key-3", 1, "default", rspb.StatusDeployed))
|
||||
body4, _ := encodeRelease(releaseStub("key-4", 1, "default", rspb.StatusDeployed))
|
||||
body5, _ := encodeRelease(releaseStub("key-5", 1, "default", rspb.StatusSuperseded))
|
||||
body6, _ := encodeRelease(releaseStub("key-6", 1, "default", rspb.StatusSuperseded))
|
||||
|
||||
sqlDriver, mock := newTestFixtureSQL(t)
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
query := fmt.Sprintf(
|
||||
"SELECT %s FROM %s WHERE %s = $1 AND %s = $2",
|
||||
sqlReleaseTableBodyColumn,
|
||||
sqlReleaseTableName,
|
||||
sqlReleaseTableOwnerColumn,
|
||||
sqlReleaseTableNamespaceColumn,
|
||||
)
|
||||
|
||||
mock.
|
||||
ExpectQuery(regexp.QuoteMeta(query)).
|
||||
WithArgs(sqlReleaseDefaultOwner, sqlDriver.namespace).
|
||||
WillReturnRows(
|
||||
mock.NewRows([]string{
|
||||
sqlReleaseTableBodyColumn,
|
||||
}).
|
||||
AddRow(body1).
|
||||
AddRow(body2).
|
||||
AddRow(body3).
|
||||
AddRow(body4).
|
||||
AddRow(body5).
|
||||
AddRow(body6),
|
||||
).RowsWillBeClosed()
|
||||
}
|
||||
|
||||
// list all deleted releases
|
||||
del, err := sqlDriver.List(func(rel *rspb.Release) bool {
|
||||
return rel.Info.Status == rspb.StatusUninstalled
|
||||
})
|
||||
// check
|
||||
if err != nil {
|
||||
t.Errorf("Failed to list deleted: %v", err)
|
||||
}
|
||||
if len(del) != 2 {
|
||||
t.Errorf("Expected 2 deleted, got %d:\n%v\n", len(del), del)
|
||||
}
|
||||
|
||||
// list all deployed releases
|
||||
dpl, err := sqlDriver.List(func(rel *rspb.Release) bool {
|
||||
return rel.Info.Status == rspb.StatusDeployed
|
||||
})
|
||||
// check
|
||||
if err != nil {
|
||||
t.Errorf("Failed to list deployed: %v", err)
|
||||
}
|
||||
if len(dpl) != 2 {
|
||||
t.Errorf("Expected 2 deployed, got %d:\n%v\n", len(dpl), dpl)
|
||||
}
|
||||
|
||||
// list all superseded releases
|
||||
ssd, err := sqlDriver.List(func(rel *rspb.Release) bool {
|
||||
return rel.Info.Status == rspb.StatusSuperseded
|
||||
})
|
||||
// check
|
||||
if err != nil {
|
||||
t.Errorf("Failed to list superseded: %v", err)
|
||||
}
|
||||
if len(ssd) != 2 {
|
||||
t.Errorf("Expected 2 superseded, got %d:\n%v\n", len(ssd), ssd)
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("sql expectations weren't met: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSqlCreate(t *testing.T) {
|
||||
vers := 1
|
||||
name := "smug-pigeon"
|
||||
namespace := "default"
|
||||
key := testKey(name, vers)
|
||||
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
|
||||
|
||||
sqlDriver, mock := newTestFixtureSQL(t)
|
||||
body, _ := encodeRelease(rel)
|
||||
|
||||
query := fmt.Sprintf(
|
||||
"INSERT INTO %s (%s,%s,%s,%s,%s,%s,%s,%s,%s) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)",
|
||||
sqlReleaseTableName,
|
||||
sqlReleaseTableKeyColumn,
|
||||
sqlReleaseTableTypeColumn,
|
||||
sqlReleaseTableBodyColumn,
|
||||
sqlReleaseTableNameColumn,
|
||||
sqlReleaseTableNamespaceColumn,
|
||||
sqlReleaseTableVersionColumn,
|
||||
sqlReleaseTableStatusColumn,
|
||||
sqlReleaseTableOwnerColumn,
|
||||
sqlReleaseTableCreatedAtColumn,
|
||||
)
|
||||
|
||||
mock.ExpectBegin()
|
||||
mock.
|
||||
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))
|
||||
mock.ExpectCommit()
|
||||
|
||||
if err := sqlDriver.Create(key, rel); err != nil {
|
||||
t.Fatalf("failed to create release with key %s: %v", key, err)
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("sql expectations weren't met: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSqlCreateAlreadyExists(t *testing.T) {
|
||||
vers := 1
|
||||
name := "smug-pigeon"
|
||||
namespace := "default"
|
||||
key := testKey(name, vers)
|
||||
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
|
||||
|
||||
sqlDriver, mock := newTestFixtureSQL(t)
|
||||
body, _ := encodeRelease(rel)
|
||||
|
||||
insertQuery := fmt.Sprintf(
|
||||
"INSERT INTO %s (%s,%s,%s,%s,%s,%s,%s,%s,%s) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)",
|
||||
sqlReleaseTableName,
|
||||
sqlReleaseTableKeyColumn,
|
||||
sqlReleaseTableTypeColumn,
|
||||
sqlReleaseTableBodyColumn,
|
||||
sqlReleaseTableNameColumn,
|
||||
sqlReleaseTableNamespaceColumn,
|
||||
sqlReleaseTableVersionColumn,
|
||||
sqlReleaseTableStatusColumn,
|
||||
sqlReleaseTableOwnerColumn,
|
||||
sqlReleaseTableCreatedAtColumn,
|
||||
)
|
||||
|
||||
// Insert fails (primary key already exists)
|
||||
mock.ExpectBegin()
|
||||
mock.
|
||||
ExpectExec(regexp.QuoteMeta(insertQuery)).
|
||||
WithArgs(key, sqlReleaseDefaultType, body, rel.Name, rel.Namespace, int(rel.Version), rel.Info.Status.String(), sqlReleaseDefaultOwner, int(time.Now().Unix())).
|
||||
WillReturnError(fmt.Errorf("dialect dependent SQL error"))
|
||||
|
||||
selectQuery := fmt.Sprintf(
|
||||
regexp.QuoteMeta("SELECT %s FROM %s WHERE %s = $1 AND %s = $2"),
|
||||
sqlReleaseTableKeyColumn,
|
||||
sqlReleaseTableName,
|
||||
sqlReleaseTableKeyColumn,
|
||||
sqlReleaseTableNamespaceColumn,
|
||||
)
|
||||
|
||||
// Let's check that we do make sure the error is due to a release already existing
|
||||
mock.
|
||||
ExpectQuery(selectQuery).
|
||||
WithArgs(key, namespace).
|
||||
WillReturnRows(
|
||||
mock.NewRows([]string{
|
||||
sqlReleaseTableKeyColumn,
|
||||
}).AddRow(
|
||||
key,
|
||||
),
|
||||
).RowsWillBeClosed()
|
||||
mock.ExpectRollback()
|
||||
|
||||
if err := sqlDriver.Create(key, rel); err == nil {
|
||||
t.Fatalf("failed to create release with key %s: %v", key, err)
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("sql expectations weren't met: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSqlUpdate(t *testing.T) {
|
||||
vers := 1
|
||||
name := "smug-pigeon"
|
||||
namespace := "default"
|
||||
key := testKey(name, vers)
|
||||
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
|
||||
|
||||
sqlDriver, mock := newTestFixtureSQL(t)
|
||||
body, _ := encodeRelease(rel)
|
||||
|
||||
query := fmt.Sprintf(
|
||||
"UPDATE %s SET %s = $1, %s = $2, %s = $3, %s = $4, %s = $5, %s = $6 WHERE %s = $7 AND %s = $8",
|
||||
sqlReleaseTableName,
|
||||
sqlReleaseTableBodyColumn,
|
||||
sqlReleaseTableNameColumn,
|
||||
sqlReleaseTableVersionColumn,
|
||||
sqlReleaseTableStatusColumn,
|
||||
sqlReleaseTableOwnerColumn,
|
||||
sqlReleaseTableModifiedAtColumn,
|
||||
sqlReleaseTableKeyColumn,
|
||||
sqlReleaseTableNamespaceColumn,
|
||||
)
|
||||
|
||||
mock.
|
||||
ExpectExec(regexp.QuoteMeta(query)).
|
||||
WithArgs(body, rel.Name, int(rel.Version), rel.Info.Status.String(), sqlReleaseDefaultOwner, int(time.Now().Unix()), key, namespace).
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
|
||||
if err := sqlDriver.Update(key, rel); err != nil {
|
||||
t.Fatalf("failed to update release with key %s: %v", key, err)
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("sql expectations weren't met: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSqlQuery(t *testing.T) {
|
||||
// Reflect actual use cases in ../storage.go
|
||||
labelSetDeployed := map[string]string{
|
||||
"name": "smug-pigeon",
|
||||
"owner": sqlReleaseDefaultOwner,
|
||||
"status": "deployed",
|
||||
}
|
||||
labelSetAll := map[string]string{
|
||||
"name": "smug-pigeon",
|
||||
"owner": sqlReleaseDefaultOwner,
|
||||
}
|
||||
|
||||
supersededRelease := releaseStub("smug-pigeon", 1, "default", rspb.StatusSuperseded)
|
||||
supersededReleaseBody, _ := encodeRelease(supersededRelease)
|
||||
deployedRelease := releaseStub("smug-pigeon", 2, "default", rspb.StatusDeployed)
|
||||
deployedReleaseBody, _ := encodeRelease(deployedRelease)
|
||||
|
||||
// Let's actually start our test
|
||||
sqlDriver, mock := newTestFixtureSQL(t)
|
||||
|
||||
query := fmt.Sprintf(
|
||||
"SELECT %s FROM %s WHERE %s = $1 AND %s = $2 AND %s = $3 AND %s = $4",
|
||||
sqlReleaseTableBodyColumn,
|
||||
sqlReleaseTableName,
|
||||
sqlReleaseTableNameColumn,
|
||||
sqlReleaseTableOwnerColumn,
|
||||
sqlReleaseTableStatusColumn,
|
||||
sqlReleaseTableNamespaceColumn,
|
||||
)
|
||||
|
||||
mock.
|
||||
ExpectQuery(regexp.QuoteMeta(query)).
|
||||
WithArgs("smug-pigeon", sqlReleaseDefaultOwner, "deployed", "default").
|
||||
WillReturnRows(
|
||||
mock.NewRows([]string{
|
||||
sqlReleaseTableBodyColumn,
|
||||
}).AddRow(
|
||||
deployedReleaseBody,
|
||||
),
|
||||
).RowsWillBeClosed()
|
||||
|
||||
query = fmt.Sprintf(
|
||||
"SELECT %s FROM %s WHERE %s = $1 AND %s = $2 AND %s = $3",
|
||||
sqlReleaseTableBodyColumn,
|
||||
sqlReleaseTableName,
|
||||
sqlReleaseTableNameColumn,
|
||||
sqlReleaseTableOwnerColumn,
|
||||
sqlReleaseTableNamespaceColumn,
|
||||
)
|
||||
|
||||
mock.
|
||||
ExpectQuery(regexp.QuoteMeta(query)).
|
||||
WithArgs("smug-pigeon", sqlReleaseDefaultOwner, "default").
|
||||
WillReturnRows(
|
||||
mock.NewRows([]string{
|
||||
sqlReleaseTableBodyColumn,
|
||||
}).AddRow(
|
||||
supersededReleaseBody,
|
||||
).AddRow(
|
||||
deployedReleaseBody,
|
||||
),
|
||||
).RowsWillBeClosed()
|
||||
|
||||
results, err := sqlDriver.Query(labelSetDeployed)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to query for deployed smug-pigeon release: %v", err)
|
||||
}
|
||||
|
||||
for _, res := range results {
|
||||
if !reflect.DeepEqual(res, deployedRelease) {
|
||||
t.Errorf("Expected release {%v}, got {%v}", deployedRelease, res)
|
||||
}
|
||||
}
|
||||
|
||||
results, err = sqlDriver.Query(labelSetAll)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to query release history for smug-pigeon: %v", err)
|
||||
}
|
||||
|
||||
if len(results) != 2 {
|
||||
t.Errorf("expected a resultset of size 2, got %d", len(results))
|
||||
}
|
||||
|
||||
for _, res := range results {
|
||||
if !reflect.DeepEqual(res, deployedRelease) && !reflect.DeepEqual(res, supersededRelease) {
|
||||
t.Errorf("Expected release {%v} or {%v}, got {%v}", deployedRelease, supersededRelease, res)
|
||||
}
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("sql expectations weren't met: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSqlDelete(t *testing.T) {
|
||||
vers := 1
|
||||
name := "smug-pigeon"
|
||||
namespace := "default"
|
||||
key := testKey(name, vers)
|
||||
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
|
||||
|
||||
body, _ := encodeRelease(rel)
|
||||
|
||||
sqlDriver, mock := newTestFixtureSQL(t)
|
||||
|
||||
selectQuery := fmt.Sprintf(
|
||||
"SELECT %s FROM %s WHERE %s = $1 AND %s = $2",
|
||||
sqlReleaseTableBodyColumn,
|
||||
sqlReleaseTableName,
|
||||
sqlReleaseTableKeyColumn,
|
||||
sqlReleaseTableNamespaceColumn,
|
||||
)
|
||||
|
||||
mock.ExpectBegin()
|
||||
mock.
|
||||
ExpectQuery(regexp.QuoteMeta(selectQuery)).
|
||||
WithArgs(key, namespace).
|
||||
WillReturnRows(
|
||||
mock.NewRows([]string{
|
||||
sqlReleaseTableBodyColumn,
|
||||
}).AddRow(
|
||||
body,
|
||||
),
|
||||
).RowsWillBeClosed()
|
||||
|
||||
deleteQuery := fmt.Sprintf(
|
||||
"DELETE FROM %s WHERE %s = $1 AND %s = $2",
|
||||
sqlReleaseTableName,
|
||||
sqlReleaseTableKeyColumn,
|
||||
sqlReleaseTableNamespaceColumn,
|
||||
)
|
||||
|
||||
mock.
|
||||
ExpectExec(regexp.QuoteMeta(deleteQuery)).
|
||||
WithArgs(key, namespace).
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
mock.ExpectCommit()
|
||||
|
||||
deletedRelease, err := sqlDriver.Delete(key)
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("sql expectations weren't met: %v", err)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("failed to delete release with key %q: %v", key, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(rel, deletedRelease) {
|
||||
t.Errorf("Expected release {%v}, got {%v}", rel, deletedRelease)
|
||||
}
|
||||
}
|
Loading…
Reference in new issue