github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/pkg/storage/driver/sql.go (about)

     1  /*
     2  Copyright The Helm Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package driver // import "github.com/stefanmcshane/helm/pkg/storage/driver"
    18  
    19  import (
    20  	"fmt"
    21  	"sort"
    22  	"time"
    23  
    24  	"github.com/jmoiron/sqlx"
    25  	migrate "github.com/rubenv/sql-migrate"
    26  
    27  	sq "github.com/Masterminds/squirrel"
    28  
    29  	// Import pq for postgres dialect
    30  	_ "github.com/lib/pq"
    31  
    32  	rspb "github.com/stefanmcshane/helm/pkg/release"
    33  )
    34  
    35  var _ Driver = (*SQL)(nil)
    36  
    37  var labelMap = map[string]struct{}{
    38  	"modifiedAt": {},
    39  	"createdAt":  {},
    40  	"version":    {},
    41  	"status":     {},
    42  	"owner":      {},
    43  	"name":       {},
    44  }
    45  
    46  const postgreSQLDialect = "postgres"
    47  
    48  // SQLDriverName is the string name of this driver.
    49  const SQLDriverName = "SQL"
    50  
    51  const sqlReleaseTableName = "releases_v1"
    52  
    53  const (
    54  	sqlReleaseTableKeyColumn        = "key"
    55  	sqlReleaseTableTypeColumn       = "type"
    56  	sqlReleaseTableBodyColumn       = "body"
    57  	sqlReleaseTableNameColumn       = "name"
    58  	sqlReleaseTableNamespaceColumn  = "namespace"
    59  	sqlReleaseTableVersionColumn    = "version"
    60  	sqlReleaseTableStatusColumn     = "status"
    61  	sqlReleaseTableOwnerColumn      = "owner"
    62  	sqlReleaseTableCreatedAtColumn  = "createdAt"
    63  	sqlReleaseTableModifiedAtColumn = "modifiedAt"
    64  )
    65  
    66  const (
    67  	sqlReleaseDefaultOwner = "helm"
    68  	sqlReleaseDefaultType  = "helm.sh/release.v1"
    69  )
    70  
    71  // SQL is the sql storage driver implementation.
    72  type SQL struct {
    73  	db               *sqlx.DB
    74  	namespace        string
    75  	statementBuilder sq.StatementBuilderType
    76  
    77  	Log func(string, ...interface{})
    78  }
    79  
    80  // Name returns the name of the driver.
    81  func (s *SQL) Name() string {
    82  	return SQLDriverName
    83  }
    84  
    85  func (s *SQL) ensureDBSetup() error {
    86  	// Populate the database with the relations we need if they don't exist yet
    87  	migrations := &migrate.MemoryMigrationSource{
    88  		Migrations: []*migrate.Migration{
    89  			{
    90  				Id: "init",
    91  				Up: []string{
    92  					fmt.Sprintf(`
    93  						CREATE TABLE %s (
    94  							%s VARCHAR(67),
    95  							%s VARCHAR(64) NOT NULL,
    96  							%s TEXT NOT NULL,
    97  							%s VARCHAR(64) NOT NULL,
    98  							%s VARCHAR(64) NOT NULL,
    99  							%s INTEGER NOT NULL,
   100  							%s TEXT NOT NULL,
   101  							%s TEXT NOT NULL,
   102  							%s INTEGER NOT NULL,
   103  							%s INTEGER NOT NULL DEFAULT 0,
   104  							PRIMARY KEY(%s, %s)
   105  						);
   106  						CREATE INDEX ON %s (%s, %s);
   107  						CREATE INDEX ON %s (%s);
   108  						CREATE INDEX ON %s (%s);
   109  						CREATE INDEX ON %s (%s);
   110  						CREATE INDEX ON %s (%s);
   111  						CREATE INDEX ON %s (%s);
   112  						
   113  						GRANT ALL ON %s TO PUBLIC;
   114  
   115  						ALTER TABLE %s ENABLE ROW LEVEL SECURITY;
   116  					`,
   117  						sqlReleaseTableName,
   118  						sqlReleaseTableKeyColumn,
   119  						sqlReleaseTableTypeColumn,
   120  						sqlReleaseTableBodyColumn,
   121  						sqlReleaseTableNameColumn,
   122  						sqlReleaseTableNamespaceColumn,
   123  						sqlReleaseTableVersionColumn,
   124  						sqlReleaseTableStatusColumn,
   125  						sqlReleaseTableOwnerColumn,
   126  						sqlReleaseTableCreatedAtColumn,
   127  						sqlReleaseTableModifiedAtColumn,
   128  						sqlReleaseTableKeyColumn,
   129  						sqlReleaseTableNamespaceColumn,
   130  						sqlReleaseTableName,
   131  						sqlReleaseTableKeyColumn,
   132  						sqlReleaseTableNamespaceColumn,
   133  						sqlReleaseTableName,
   134  						sqlReleaseTableVersionColumn,
   135  						sqlReleaseTableName,
   136  						sqlReleaseTableStatusColumn,
   137  						sqlReleaseTableName,
   138  						sqlReleaseTableOwnerColumn,
   139  						sqlReleaseTableName,
   140  						sqlReleaseTableCreatedAtColumn,
   141  						sqlReleaseTableName,
   142  						sqlReleaseTableModifiedAtColumn,
   143  						sqlReleaseTableName,
   144  						sqlReleaseTableName,
   145  					),
   146  				},
   147  				Down: []string{
   148  					fmt.Sprintf(`
   149  						DROP TABLE %s;
   150  					`, sqlReleaseTableName),
   151  				},
   152  			},
   153  		},
   154  	}
   155  
   156  	_, err := migrate.Exec(s.db.DB, postgreSQLDialect, migrations, migrate.Up)
   157  	return err
   158  }
   159  
   160  // SQLReleaseWrapper describes how Helm releases are stored in an SQL database
   161  type SQLReleaseWrapper struct {
   162  	// The primary key, made of {release-name}.{release-version}
   163  	Key string `db:"key"`
   164  
   165  	// See https://github.com/helm/helm/blob/c9fe3d118caec699eb2565df9838673af379ce12/pkg/storage/driver/secrets.go#L231
   166  	Type string `db:"type"`
   167  
   168  	// The rspb.Release body, as a base64-encoded string
   169  	Body string `db:"body"`
   170  
   171  	// Release "labels" that can be used as filters in the storage.Query(labels map[string]string)
   172  	// we implemented. Note that allowing Helm users to filter against new dimensions will require a
   173  	// new migration to be added, and the Create and/or update functions to be updated accordingly.
   174  	Name       string `db:"name"`
   175  	Namespace  string `db:"namespace"`
   176  	Version    int    `db:"version"`
   177  	Status     string `db:"status"`
   178  	Owner      string `db:"owner"`
   179  	CreatedAt  int    `db:"createdAt"`
   180  	ModifiedAt int    `db:"modifiedAt"`
   181  }
   182  
   183  // NewSQL initializes a new sql driver.
   184  func NewSQL(connectionString string, logger func(string, ...interface{}), namespace string) (*SQL, error) {
   185  	db, err := sqlx.Connect(postgreSQLDialect, connectionString)
   186  	if err != nil {
   187  		return nil, err
   188  	}
   189  
   190  	driver := &SQL{
   191  		db:               db,
   192  		Log:              logger,
   193  		statementBuilder: sq.StatementBuilder.PlaceholderFormat(sq.Dollar),
   194  	}
   195  
   196  	if err := driver.ensureDBSetup(); err != nil {
   197  		return nil, err
   198  	}
   199  
   200  	driver.namespace = namespace
   201  
   202  	return driver, nil
   203  }
   204  
   205  // Get returns the release named by key.
   206  func (s *SQL) Get(key string) (*rspb.Release, error) {
   207  	var record SQLReleaseWrapper
   208  
   209  	qb := s.statementBuilder.
   210  		Select(sqlReleaseTableBodyColumn).
   211  		From(sqlReleaseTableName).
   212  		Where(sq.Eq{sqlReleaseTableKeyColumn: key}).
   213  		Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace})
   214  
   215  	query, args, err := qb.ToSql()
   216  	if err != nil {
   217  		s.Log("failed to build query: %v", err)
   218  		return nil, err
   219  	}
   220  
   221  	// Get will return an error if the result is empty
   222  	if err := s.db.Get(&record, query, args...); err != nil {
   223  		s.Log("got SQL error when getting release %s: %v", key, err)
   224  		return nil, ErrReleaseNotFound
   225  	}
   226  
   227  	release, err := decodeRelease(record.Body)
   228  	if err != nil {
   229  		s.Log("get: failed to decode data %q: %v", key, err)
   230  		return nil, err
   231  	}
   232  
   233  	return release, nil
   234  }
   235  
   236  // List returns the list of all releases such that filter(release) == true
   237  func (s *SQL) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) {
   238  	sb := s.statementBuilder.
   239  		Select(sqlReleaseTableBodyColumn).
   240  		From(sqlReleaseTableName).
   241  		Where(sq.Eq{sqlReleaseTableOwnerColumn: sqlReleaseDefaultOwner})
   242  
   243  	// If a namespace was specified, we only list releases from that namespace
   244  	if s.namespace != "" {
   245  		sb = sb.Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace})
   246  	}
   247  
   248  	query, args, err := sb.ToSql()
   249  	if err != nil {
   250  		s.Log("failed to build query: %v", err)
   251  		return nil, err
   252  	}
   253  
   254  	var records = []SQLReleaseWrapper{}
   255  	if err := s.db.Select(&records, query, args...); err != nil {
   256  		s.Log("list: failed to list: %v", err)
   257  		return nil, err
   258  	}
   259  
   260  	var releases []*rspb.Release
   261  	for _, record := range records {
   262  		release, err := decodeRelease(record.Body)
   263  		if err != nil {
   264  			s.Log("list: failed to decode release: %v: %v", record, err)
   265  			continue
   266  		}
   267  		if filter(release) {
   268  			releases = append(releases, release)
   269  		}
   270  	}
   271  
   272  	return releases, nil
   273  }
   274  
   275  // Query returns the set of releases that match the provided set of labels.
   276  func (s *SQL) Query(labels map[string]string) ([]*rspb.Release, error) {
   277  	sb := s.statementBuilder.
   278  		Select(sqlReleaseTableBodyColumn).
   279  		From(sqlReleaseTableName)
   280  
   281  	keys := make([]string, 0, len(labels))
   282  	for key := range labels {
   283  		keys = append(keys, key)
   284  	}
   285  	sort.Strings(keys)
   286  	for _, key := range keys {
   287  		if _, ok := labelMap[key]; ok {
   288  			sb = sb.Where(sq.Eq{key: labels[key]})
   289  		} else {
   290  			s.Log("unknown label %s", key)
   291  			return nil, fmt.Errorf("unknown label %s", key)
   292  		}
   293  	}
   294  
   295  	// If a namespace was specified, we only list releases from that namespace
   296  	if s.namespace != "" {
   297  		sb = sb.Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace})
   298  	}
   299  
   300  	// Build our query
   301  	query, args, err := sb.ToSql()
   302  	if err != nil {
   303  		s.Log("failed to build query: %v", err)
   304  		return nil, err
   305  	}
   306  
   307  	var records = []SQLReleaseWrapper{}
   308  	if err := s.db.Select(&records, query, args...); err != nil {
   309  		s.Log("list: failed to query with labels: %v", err)
   310  		return nil, err
   311  	}
   312  
   313  	if len(records) == 0 {
   314  		return nil, ErrReleaseNotFound
   315  	}
   316  
   317  	var releases []*rspb.Release
   318  	for _, record := range records {
   319  		release, err := decodeRelease(record.Body)
   320  		if err != nil {
   321  			s.Log("list: failed to decode release: %v: %v", record, err)
   322  			continue
   323  		}
   324  		releases = append(releases, release)
   325  	}
   326  
   327  	if len(releases) == 0 {
   328  		return nil, ErrReleaseNotFound
   329  	}
   330  
   331  	return releases, nil
   332  }
   333  
   334  // Create creates a new release.
   335  func (s *SQL) Create(key string, rls *rspb.Release) error {
   336  	namespace := rls.Namespace
   337  	if namespace == "" {
   338  		namespace = defaultNamespace
   339  	}
   340  	s.namespace = namespace
   341  
   342  	body, err := encodeRelease(rls)
   343  	if err != nil {
   344  		s.Log("failed to encode release: %v", err)
   345  		return err
   346  	}
   347  
   348  	transaction, err := s.db.Beginx()
   349  	if err != nil {
   350  		s.Log("failed to start SQL transaction: %v", err)
   351  		return fmt.Errorf("error beginning transaction: %v", err)
   352  	}
   353  
   354  	insertQuery, args, err := s.statementBuilder.
   355  		Insert(sqlReleaseTableName).
   356  		Columns(
   357  			sqlReleaseTableKeyColumn,
   358  			sqlReleaseTableTypeColumn,
   359  			sqlReleaseTableBodyColumn,
   360  			sqlReleaseTableNameColumn,
   361  			sqlReleaseTableNamespaceColumn,
   362  			sqlReleaseTableVersionColumn,
   363  			sqlReleaseTableStatusColumn,
   364  			sqlReleaseTableOwnerColumn,
   365  			sqlReleaseTableCreatedAtColumn,
   366  		).
   367  		Values(
   368  			key,
   369  			sqlReleaseDefaultType,
   370  			body,
   371  			rls.Name,
   372  			namespace,
   373  			int(rls.Version),
   374  			rls.Info.Status.String(),
   375  			sqlReleaseDefaultOwner,
   376  			int(time.Now().Unix()),
   377  		).ToSql()
   378  	if err != nil {
   379  		s.Log("failed to build insert query: %v", err)
   380  		return err
   381  	}
   382  
   383  	if _, err := transaction.Exec(insertQuery, args...); err != nil {
   384  		defer transaction.Rollback()
   385  
   386  		selectQuery, args, buildErr := s.statementBuilder.
   387  			Select(sqlReleaseTableKeyColumn).
   388  			From(sqlReleaseTableName).
   389  			Where(sq.Eq{sqlReleaseTableKeyColumn: key}).
   390  			Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}).
   391  			ToSql()
   392  		if buildErr != nil {
   393  			s.Log("failed to build select query: %v", buildErr)
   394  			return err
   395  		}
   396  
   397  		var record SQLReleaseWrapper
   398  		if err := transaction.Get(&record, selectQuery, args...); err == nil {
   399  			s.Log("release %s already exists", key)
   400  			return ErrReleaseExists
   401  		}
   402  
   403  		s.Log("failed to store release %s in SQL database: %v", key, err)
   404  		return err
   405  	}
   406  	defer transaction.Commit()
   407  
   408  	return nil
   409  }
   410  
   411  // Update updates a release.
   412  func (s *SQL) Update(key string, rls *rspb.Release) error {
   413  	namespace := rls.Namespace
   414  	if namespace == "" {
   415  		namespace = defaultNamespace
   416  	}
   417  	s.namespace = namespace
   418  
   419  	body, err := encodeRelease(rls)
   420  	if err != nil {
   421  		s.Log("failed to encode release: %v", err)
   422  		return err
   423  	}
   424  
   425  	query, args, err := s.statementBuilder.
   426  		Update(sqlReleaseTableName).
   427  		Set(sqlReleaseTableBodyColumn, body).
   428  		Set(sqlReleaseTableNameColumn, rls.Name).
   429  		Set(sqlReleaseTableVersionColumn, int(rls.Version)).
   430  		Set(sqlReleaseTableStatusColumn, rls.Info.Status.String()).
   431  		Set(sqlReleaseTableOwnerColumn, sqlReleaseDefaultOwner).
   432  		Set(sqlReleaseTableModifiedAtColumn, int(time.Now().Unix())).
   433  		Where(sq.Eq{sqlReleaseTableKeyColumn: key}).
   434  		Where(sq.Eq{sqlReleaseTableNamespaceColumn: namespace}).
   435  		ToSql()
   436  
   437  	if err != nil {
   438  		s.Log("failed to build update query: %v", err)
   439  		return err
   440  	}
   441  
   442  	if _, err := s.db.Exec(query, args...); err != nil {
   443  		s.Log("failed to update release %s in SQL database: %v", key, err)
   444  		return err
   445  	}
   446  
   447  	return nil
   448  }
   449  
   450  // Delete deletes a release or returns ErrReleaseNotFound.
   451  func (s *SQL) Delete(key string) (*rspb.Release, error) {
   452  	transaction, err := s.db.Beginx()
   453  	if err != nil {
   454  		s.Log("failed to start SQL transaction: %v", err)
   455  		return nil, fmt.Errorf("error beginning transaction: %v", err)
   456  	}
   457  
   458  	selectQuery, args, err := s.statementBuilder.
   459  		Select(sqlReleaseTableBodyColumn).
   460  		From(sqlReleaseTableName).
   461  		Where(sq.Eq{sqlReleaseTableKeyColumn: key}).
   462  		Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}).
   463  		ToSql()
   464  	if err != nil {
   465  		s.Log("failed to build select query: %v", err)
   466  		return nil, err
   467  	}
   468  
   469  	var record SQLReleaseWrapper
   470  	err = transaction.Get(&record, selectQuery, args...)
   471  	if err != nil {
   472  		s.Log("release %s not found: %v", key, err)
   473  		return nil, ErrReleaseNotFound
   474  	}
   475  
   476  	release, err := decodeRelease(record.Body)
   477  	if err != nil {
   478  		s.Log("failed to decode release %s: %v", key, err)
   479  		transaction.Rollback()
   480  		return nil, err
   481  	}
   482  	defer transaction.Commit()
   483  
   484  	deleteQuery, args, err := s.statementBuilder.
   485  		Delete(sqlReleaseTableName).
   486  		Where(sq.Eq{sqlReleaseTableKeyColumn: key}).
   487  		Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}).
   488  		ToSql()
   489  	if err != nil {
   490  		s.Log("failed to build select query: %v", err)
   491  		return nil, err
   492  	}
   493  
   494  	_, err = transaction.Exec(deleteQuery, args...)
   495  	return release, err
   496  }