mirror of https://github.com/helm/helm
Signed-off-by: Elliot Maincourt <e.maincourt@gmail.com>pull/7635/head
parent
f860e08f2d
commit
8858a079b6
@ -0,0 +1,339 @@
|
|||||||
|
/*
|
||||||
|
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"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
migrate "github.com/rubenv/sql-migrate"
|
||||||
|
|
||||||
|
// Import pq for potgres dialect
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
|
||||||
|
storageerrors "k8s.io/helm/pkg/storage/errors"
|
||||||
|
|
||||||
|
rspb "helm.sh/helm/v3/pkg/release"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Driver = (*SQL)(nil)
|
||||||
|
|
||||||
|
var labelMap = map[string]struct{}{
|
||||||
|
"modifiedAt": {},
|
||||||
|
"createdAt": {},
|
||||||
|
"version": {},
|
||||||
|
"status": {},
|
||||||
|
"owner": {},
|
||||||
|
"name": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
var supportedSQLDialects = map[string]struct{}{
|
||||||
|
"postgres": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// SQLDriverName is the string name of this driver.
|
||||||
|
const SQLDriverName = "SQL"
|
||||||
|
|
||||||
|
// SQL is the sql storage driver implementation.
|
||||||
|
type SQL struct {
|
||||||
|
db *sqlx.DB
|
||||||
|
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{
|
||||||
|
`
|
||||||
|
CREATE TABLE releases (
|
||||||
|
key VARCHAR(67) PRIMARY KEY,
|
||||||
|
type VARCHAR(64) NOT NULL,
|
||||||
|
body TEXT NOT NULL,
|
||||||
|
name VARCHAR(64) NOT NULL,
|
||||||
|
version INTEGER NOT NULL,
|
||||||
|
status TEXT NOT NULL,
|
||||||
|
owner TEXT NOT NULL,
|
||||||
|
createdAt INTEGER NOT NULL,
|
||||||
|
modifiedAt INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
CREATE INDEX ON releases (key);
|
||||||
|
CREATE INDEX ON releases (version);
|
||||||
|
CREATE INDEX ON releases (status);
|
||||||
|
CREATE INDEX ON releases (owner);
|
||||||
|
CREATE INDEX ON releases (createdAt);
|
||||||
|
CREATE INDEX ON releases (modifiedAt);
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
Down: []string{
|
||||||
|
`
|
||||||
|
DROP TABLE releases;
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := migrate.Exec(s.db.DB, "postgres", 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"`
|
||||||
|
Version int `db:"version"`
|
||||||
|
Status string `db:"status"`
|
||||||
|
Owner string `db:"owner"`
|
||||||
|
CreatedAt int `db:"createdAt"`
|
||||||
|
ModifiedAt int `db:"modifiedAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSQL initializes a new memory driver.
|
||||||
|
func NewSQL(dialect, connectionString string, logger func(string, ...interface{})) (*SQL, error) {
|
||||||
|
if _, ok := supportedSQLDialects[dialect]; !ok {
|
||||||
|
return nil, fmt.Errorf("%s dialect isn't supported, only \"postgres\" is available for now", dialect)
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err := sqlx.Connect(dialect, connectionString)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
driver := &SQL{
|
||||||
|
db: db,
|
||||||
|
Log: logger,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := driver.ensureDBSetup(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return driver, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the release named by key.
|
||||||
|
func (s *SQL) Get(key string) (*rspb.Release, error) {
|
||||||
|
var record SQLReleaseWrapper
|
||||||
|
// Get will return an error if the result is empty
|
||||||
|
err := s.db.Get(&record, "SELECT body FROM releases WHERE key = $1", key)
|
||||||
|
if err != nil {
|
||||||
|
s.Log("got SQL error when getting release %s: %v", key, err)
|
||||||
|
return nil, storageerrors.ErrReleaseNotFound(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
var records = []SQLReleaseWrapper{}
|
||||||
|
if err := s.db.Select(&records, "SELECT body FROM releases WHERE owner = 'helm'"); 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) {
|
||||||
|
var sqlFilterKeys []string
|
||||||
|
sqlFilter := map[string]interface{}{}
|
||||||
|
for key, val := range labels {
|
||||||
|
// Build a slice of where filters e.g
|
||||||
|
// labels = map[string]string{ "foo": "foo", "bar": "bar" }
|
||||||
|
// []string{ "foo=?", "bar=?" }
|
||||||
|
if _, ok := labelMap[key]; ok {
|
||||||
|
sqlFilterKeys = append(sqlFilterKeys, strings.Join([]string{key, "=:", key}, ""))
|
||||||
|
sqlFilter[key] = val
|
||||||
|
} else {
|
||||||
|
s.Log("unknown label %s", key)
|
||||||
|
return nil, fmt.Errorf("unknow label %s", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(sqlFilterKeys)
|
||||||
|
|
||||||
|
// Build our query
|
||||||
|
query := strings.Join([]string{
|
||||||
|
"SELECT body FROM releases",
|
||||||
|
"WHERE",
|
||||||
|
strings.Join(sqlFilterKeys, " AND "),
|
||||||
|
}, " ")
|
||||||
|
|
||||||
|
rows, err := s.db.NamedQuery(query, sqlFilter)
|
||||||
|
if err != nil {
|
||||||
|
s.Log("failed to query with labels: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var releases []*rspb.Release
|
||||||
|
for rows.Next() {
|
||||||
|
var record SQLReleaseWrapper
|
||||||
|
if err = rows.StructScan(&record); err != nil {
|
||||||
|
s.Log("failed to scan record %q: %v", record, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
release, err := decodeRelease(record.Body)
|
||||||
|
if err != nil {
|
||||||
|
s.Log("failed to decode release: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
releases = append(releases, release)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(releases) == 0 {
|
||||||
|
return nil, storageerrors.ErrReleaseNotFound(labels["name"])
|
||||||
|
}
|
||||||
|
|
||||||
|
return releases, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create creates a new release.
|
||||||
|
func (s *SQL) Create(key string, rls *rspb.Release) error {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := transaction.NamedExec("INSERT INTO releases (key, type, body, name, version, status, owner, createdAt) VALUES (:key, :type, :body, :name, :version, :status, :owner, :createdAt)",
|
||||||
|
&SQLReleaseWrapper{
|
||||||
|
Key: key,
|
||||||
|
Type: "helm.sh/release.v1",
|
||||||
|
Body: body,
|
||||||
|
|
||||||
|
Name: rls.Name,
|
||||||
|
Version: int(rls.Version),
|
||||||
|
Status: rls.Info.Status.String(),
|
||||||
|
Owner: "helm",
|
||||||
|
CreatedAt: int(time.Now().Unix()),
|
||||||
|
},
|
||||||
|
); err != nil {
|
||||||
|
defer transaction.Rollback()
|
||||||
|
var record SQLReleaseWrapper
|
||||||
|
if err := transaction.Get(&record, "SELECT key FROM releases WHERE key = ?", key); err == nil {
|
||||||
|
s.Log("release %s already exists", key)
|
||||||
|
return storageerrors.ErrReleaseExists(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
body, err := encodeRelease(rls)
|
||||||
|
if err != nil {
|
||||||
|
s.Log("failed to encode release: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := s.db.NamedExec("UPDATE releases SET body=:body, name=:name, version=:version, status=:status, owner=:owner, modifiedAt=:modifiedAt WHERE key=:key",
|
||||||
|
&SQLReleaseWrapper{
|
||||||
|
Key: key,
|
||||||
|
Body: body,
|
||||||
|
Name: rls.Name,
|
||||||
|
Version: int(rls.Version),
|
||||||
|
Status: rls.Info.Status.String(),
|
||||||
|
Owner: "helm",
|
||||||
|
ModifiedAt: int(time.Now().Unix()),
|
||||||
|
},
|
||||||
|
); 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
var record SQLReleaseWrapper
|
||||||
|
err = transaction.Get(&record, "SELECT body FROM releases WHERE key = $1", key)
|
||||||
|
if err != nil {
|
||||||
|
s.Log("release %s not found: %v", key, err)
|
||||||
|
return nil, storageerrors.ErrReleaseNotFound(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
_, err = transaction.Exec("DELETE FROM releases WHERE key = $1", key)
|
||||||
|
return release, err
|
||||||
|
}
|
@ -0,0 +1,342 @@
|
|||||||
|
/*
|
||||||
|
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)
|
||||||
|
mock.
|
||||||
|
ExpectQuery("SELECT body FROM releases WHERE key = ?").
|
||||||
|
WithArgs(key).
|
||||||
|
WillReturnRows(
|
||||||
|
mock.NewRows([]string{
|
||||||
|
"body",
|
||||||
|
}).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++ {
|
||||||
|
mock.
|
||||||
|
ExpectQuery("SELECT body FROM releases WHERE owner = 'helm'").
|
||||||
|
WillReturnRows(
|
||||||
|
mock.NewRows([]string{
|
||||||
|
"body",
|
||||||
|
}).
|
||||||
|
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)
|
||||||
|
|
||||||
|
mock.ExpectBegin()
|
||||||
|
mock.
|
||||||
|
ExpectExec(regexp.QuoteMeta("INSERT INTO releases (key, type, body, name, version, status, owner, createdAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")).
|
||||||
|
WithArgs(key, "helm.sh/release.v1", body, rel.Name, int(rel.Version), rel.Info.Status.String(), "helm", 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)
|
||||||
|
|
||||||
|
// Insert fails (primary key already exists)
|
||||||
|
mock.ExpectBegin()
|
||||||
|
mock.
|
||||||
|
ExpectExec(regexp.QuoteMeta("INSERT INTO releases (key, type, body, name, version, status, owner, createdAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")).
|
||||||
|
WithArgs(key, "helm.sh/release.v1", body, rel.Name, int(rel.Version), rel.Info.Status.String(), "helm", int(time.Now().Unix())).
|
||||||
|
WillReturnError(fmt.Errorf("dialect dependent SQL error"))
|
||||||
|
|
||||||
|
// Let's check that we do make sure the error is due to a release already existing
|
||||||
|
mock.
|
||||||
|
ExpectQuery(regexp.QuoteMeta("SELECT key FROM releases WHERE key = ?")).
|
||||||
|
WithArgs(key).
|
||||||
|
WillReturnRows(
|
||||||
|
mock.NewRows([]string{
|
||||||
|
"body",
|
||||||
|
}).AddRow(
|
||||||
|
body,
|
||||||
|
),
|
||||||
|
).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)
|
||||||
|
|
||||||
|
mock.
|
||||||
|
ExpectExec(regexp.QuoteMeta("UPDATE releases SET body=?, name=?, version=?, status=?, owner=?, modifiedAt=? WHERE key=?")).
|
||||||
|
WithArgs(body, rel.Name, int(rel.Version), rel.Info.Status.String(), "helm", int(time.Now().Unix()), key).
|
||||||
|
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": "helm",
|
||||||
|
"status": "deployed",
|
||||||
|
}
|
||||||
|
labelSetAll := map[string]string{
|
||||||
|
"name": "smug-pigeon",
|
||||||
|
"owner": "helm",
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
mock.
|
||||||
|
ExpectQuery(regexp.QuoteMeta("SELECT body FROM releases WHERE name=? AND owner=? AND status=?")).
|
||||||
|
WithArgs("smug-pigeon", "helm", "deployed").
|
||||||
|
WillReturnRows(
|
||||||
|
mock.NewRows([]string{
|
||||||
|
"body",
|
||||||
|
}).AddRow(
|
||||||
|
deployedReleaseBody,
|
||||||
|
),
|
||||||
|
).RowsWillBeClosed()
|
||||||
|
|
||||||
|
mock.
|
||||||
|
ExpectQuery(regexp.QuoteMeta("SELECT body FROM releases WHERE name=? AND owner=?")).
|
||||||
|
WithArgs("smug-pigeon", "helm").
|
||||||
|
WillReturnRows(
|
||||||
|
mock.NewRows([]string{
|
||||||
|
"body",
|
||||||
|
}).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)
|
||||||
|
|
||||||
|
mock.ExpectBegin()
|
||||||
|
mock.
|
||||||
|
ExpectQuery("SELECT body FROM releases WHERE key = ?").
|
||||||
|
WithArgs(key).
|
||||||
|
WillReturnRows(
|
||||||
|
mock.NewRows([]string{
|
||||||
|
"body",
|
||||||
|
}).AddRow(
|
||||||
|
body,
|
||||||
|
),
|
||||||
|
).RowsWillBeClosed()
|
||||||
|
|
||||||
|
mock.
|
||||||
|
ExpectExec(regexp.QuoteMeta("DELETE FROM releases WHERE key = $1")).
|
||||||
|
WithArgs(key).
|
||||||
|
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||||
|
mock.ExpectCommit()
|
||||||
|
|
||||||
|
deletedRelease, err := sqlDriver.Delete(key)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := mock.ExpectationsWereMet(); err != nil {
|
||||||
|
t.Errorf("sql expectations weren't met: %v", err)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue