github.com/amtisyAts/helm@v2.17.0+incompatible/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
    18  
    19  import (
    20  	"fmt"
    21  	"sort"
    22  	"strings"
    23  	"time"
    24  
    25  	"github.com/jmoiron/sqlx"
    26  	migrate "github.com/rubenv/sql-migrate"
    27  
    28  	// Import pq for postgres dialect
    29  	_ "github.com/lib/pq"
    30  
    31  	rspb "k8s.io/helm/pkg/proto/hapi/release"
    32  	storageerrors "k8s.io/helm/pkg/storage/errors"
    33  )
    34  
    35  var _ Driver = (*SQL)(nil)
    36  
    37  var labelMap = map[string]string{
    38  	"MODIFIED_AT": "modified_at",
    39  	"CREATED_AT":  "created_at",
    40  	"VERSION":     "version",
    41  	"STATUS":      "status",
    42  	"OWNER":       "owner",
    43  	"NAME":        "name",
    44  }
    45  
    46  var supportedSQLDialects = map[string]struct{}{
    47  	"postgres": {},
    48  }
    49  
    50  // SQLDriverName is the string name of this driver.
    51  const SQLDriverName = "SQL"
    52  
    53  // SQL is the sql storage driver implementation.
    54  type SQL struct {
    55  	db  *sqlx.DB
    56  	Log func(string, ...interface{})
    57  }
    58  
    59  // Name returns the name of the driver.
    60  func (s *SQL) Name() string {
    61  	return SQLDriverName
    62  }
    63  
    64  func (s *SQL) ensureDBSetup() error {
    65  	// Populate the database with the relations we need if they don't exist yet
    66  	migrations := &migrate.MemoryMigrationSource{
    67  		Migrations: []*migrate.Migration{
    68  			{
    69  				Id: "init",
    70  				Up: []string{
    71  					`
    72  						CREATE TABLE releases (
    73  							key VARCHAR(67) PRIMARY KEY,
    74  						  body TEXT NOT NULL,
    75  
    76  						  name VARCHAR(64) NOT NULL,
    77  						  version INTEGER NOT NULL,
    78  							status TEXT NOT NULL,
    79  							owner TEXT NOT NULL,
    80  							created_at INTEGER NOT NULL,
    81  							modified_at INTEGER NOT NULL DEFAULT 0
    82  						);
    83  
    84  						CREATE INDEX ON releases (key);
    85  						CREATE INDEX ON releases (version);
    86  						CREATE INDEX ON releases (status);
    87  						CREATE INDEX ON releases (owner);
    88  						CREATE INDEX ON releases (created_at);
    89  						CREATE INDEX ON releases (modified_at);
    90  					`,
    91  				},
    92  				Down: []string{
    93  					`
    94  						 DROP TABLE releases;
    95  					`,
    96  				},
    97  			},
    98  		},
    99  	}
   100  
   101  	_, err := migrate.Exec(s.db.DB, "postgres", migrations, migrate.Up)
   102  	return err
   103  }
   104  
   105  // SQLReleaseWrapper describes how Helm releases are stored in an SQL database
   106  type SQLReleaseWrapper struct {
   107  	// The primary key, made of {release-name}.{release-version}
   108  	Key string `db:"key"`
   109  
   110  	// The rspb.Release body, as a base64-encoded string
   111  	Body string `db:"body"`
   112  
   113  	// Release "labels" that can be used as filters in the storage.Query(labels map[string]string)
   114  	// we implemented. Note that allowing Helm users to filter against new dimensions will require a
   115  	// new migration to be added, and the Create and/or update functions to be updated accordingly.
   116  	Name       string `db:"name"`
   117  	Version    int    `db:"version"`
   118  	Status     string `db:"status"`
   119  	Owner      string `db:"owner"`
   120  	CreatedAt  int    `db:"created_at"`
   121  	ModifiedAt int    `db:"modified_at"`
   122  }
   123  
   124  // NewSQL initializes a new memory driver.
   125  func NewSQL(dialect, connectionString string, logger func(string, ...interface{})) (*SQL, error) {
   126  	if _, ok := supportedSQLDialects[dialect]; !ok {
   127  		return nil, fmt.Errorf("%s dialect isn't supported, only \"postgres\" is available for now", dialect)
   128  	}
   129  
   130  	db, err := sqlx.Connect(dialect, connectionString)
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  
   135  	driver := &SQL{
   136  		db:  db,
   137  		Log: logger,
   138  	}
   139  
   140  	if err := driver.ensureDBSetup(); err != nil {
   141  		return nil, err
   142  	}
   143  
   144  	return driver, nil
   145  }
   146  
   147  // Get returns the release named by key.
   148  func (s *SQL) Get(key string) (*rspb.Release, error) {
   149  	var record SQLReleaseWrapper
   150  	// Get will return an error if the result is empty
   151  	err := s.db.Get(&record, "SELECT body FROM releases WHERE key = $1", key)
   152  	if err != nil {
   153  		s.Log("got SQL error when getting release %s: %v", key, err)
   154  		return nil, storageerrors.ErrReleaseNotFound(key)
   155  	}
   156  
   157  	release, err := decodeRelease(record.Body)
   158  	if err != nil {
   159  		s.Log("get: failed to decode data %q: %v", key, err)
   160  		return nil, err
   161  	}
   162  
   163  	return release, nil
   164  }
   165  
   166  // List returns the list of all releases such that filter(release) == true
   167  func (s *SQL) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) {
   168  	var records = []SQLReleaseWrapper{}
   169  	if err := s.db.Select(&records, "SELECT body FROM releases WHERE owner = 'TILLER'"); err != nil {
   170  		s.Log("list: failed to list: %v", err)
   171  		return nil, err
   172  	}
   173  
   174  	var releases []*rspb.Release
   175  	for _, record := range records {
   176  		release, err := decodeRelease(record.Body)
   177  		if err != nil {
   178  			s.Log("list: failed to decode release: %v: %v", record, err)
   179  			continue
   180  		}
   181  		if filter(release) {
   182  			releases = append(releases, release)
   183  		}
   184  	}
   185  
   186  	return releases, nil
   187  }
   188  
   189  // Query returns the set of releases that match the provided set of labels.
   190  func (s *SQL) Query(labels map[string]string) ([]*rspb.Release, error) {
   191  	var sqlFilterKeys []string
   192  	sqlFilter := map[string]interface{}{}
   193  	for key, val := range labels {
   194  		// Build a slice of where filters e.g
   195  		// labels = map[string]string{ "foo": "foo", "bar": "bar" }
   196  		// []string{ "foo=?", "bar=?" }
   197  		if dbField, ok := labelMap[key]; ok {
   198  			sqlFilterKeys = append(sqlFilterKeys, strings.Join([]string{dbField, "=:", dbField}, ""))
   199  			sqlFilter[dbField] = val
   200  		} else {
   201  			s.Log("unknown label %s", key)
   202  			return nil, fmt.Errorf("unknown label %s", key)
   203  		}
   204  	}
   205  	sort.Strings(sqlFilterKeys)
   206  
   207  	// Build our query
   208  	query := strings.Join([]string{
   209  		"SELECT body FROM releases",
   210  		"WHERE",
   211  		strings.Join(sqlFilterKeys, " AND "),
   212  	}, " ")
   213  
   214  	rows, err := s.db.NamedQuery(query, sqlFilter)
   215  	if err != nil {
   216  		s.Log("failed to query with labels: %v", err)
   217  		return nil, err
   218  	}
   219  
   220  	var releases []*rspb.Release
   221  	for rows.Next() {
   222  		var record SQLReleaseWrapper
   223  		if err = rows.StructScan(&record); err != nil {
   224  			s.Log("failed to scan record %q: %v", record, err)
   225  			return nil, err
   226  		}
   227  
   228  		release, err := decodeRelease(record.Body)
   229  		if err != nil {
   230  			s.Log("failed to decode release: %v", err)
   231  			continue
   232  		}
   233  		releases = append(releases, release)
   234  	}
   235  
   236  	if len(releases) == 0 {
   237  		return nil, storageerrors.ErrReleaseNotFound(labels["NAME"])
   238  	}
   239  
   240  	return releases, nil
   241  }
   242  
   243  // Create creates a new release.
   244  func (s *SQL) Create(key string, rls *rspb.Release) error {
   245  	body, err := encodeRelease(rls)
   246  	if err != nil {
   247  		s.Log("failed to encode release: %v", err)
   248  		return err
   249  	}
   250  
   251  	transaction, err := s.db.Beginx()
   252  	if err != nil {
   253  		s.Log("failed to start SQL transaction: %v", err)
   254  		return fmt.Errorf("error beginning transaction: %v", err)
   255  	}
   256  
   257  	if _, err := transaction.NamedExec("INSERT INTO releases (key, body, name, version, status, owner, created_at) VALUES (:key, :body, :name, :version, :status, :owner, :created_at)",
   258  		&SQLReleaseWrapper{
   259  			Key:  key,
   260  			Body: body,
   261  
   262  			Name:      rls.Name,
   263  			Version:   int(rls.Version),
   264  			Status:    rspb.Status_Code_name[int32(rls.Info.Status.Code)],
   265  			Owner:     "TILLER",
   266  			CreatedAt: int(time.Now().Unix()),
   267  		},
   268  	); err != nil {
   269  		defer transaction.Rollback()
   270  		var record SQLReleaseWrapper
   271  		if err := transaction.Get(&record, "SELECT key FROM releases WHERE key = ?", key); err == nil {
   272  			s.Log("release %s already exists", key)
   273  			return storageerrors.ErrReleaseExists(key)
   274  		}
   275  
   276  		s.Log("failed to store release %s in SQL database: %v", key, err)
   277  		return err
   278  	}
   279  	defer transaction.Commit()
   280  
   281  	return nil
   282  }
   283  
   284  // Update updates a release.
   285  func (s *SQL) Update(key string, rls *rspb.Release) error {
   286  	body, err := encodeRelease(rls)
   287  	if err != nil {
   288  		s.Log("failed to encode release: %v", err)
   289  		return err
   290  	}
   291  
   292  	if _, err := s.db.NamedExec("UPDATE releases SET body=:body, name=:name, version=:version, status=:status, owner=:owner, modified_at=:modified_at WHERE key=:key",
   293  		&SQLReleaseWrapper{
   294  			Key:  key,
   295  			Body: body,
   296  
   297  			Name:       rls.Name,
   298  			Version:    int(rls.Version),
   299  			Status:     rspb.Status_Code_name[int32(rls.Info.Status.Code)],
   300  			Owner:      "TILLER",
   301  			ModifiedAt: int(time.Now().Unix()),
   302  		},
   303  	); err != nil {
   304  		s.Log("failed to update release %s in SQL database: %v", key, err)
   305  		return err
   306  	}
   307  
   308  	return nil
   309  }
   310  
   311  // Delete deletes a release or returns ErrReleaseNotFound.
   312  func (s *SQL) Delete(key string) (*rspb.Release, error) {
   313  	transaction, err := s.db.Beginx()
   314  	if err != nil {
   315  		s.Log("failed to start SQL transaction: %v", err)
   316  		return nil, fmt.Errorf("error beginning transaction: %v", err)
   317  	}
   318  
   319  	var record SQLReleaseWrapper
   320  	err = transaction.Get(&record, "SELECT body FROM releases WHERE key = $1", key)
   321  	if err != nil {
   322  		s.Log("release %s not found: %v", key, err)
   323  		return nil, storageerrors.ErrReleaseNotFound(key)
   324  	}
   325  
   326  	release, err := decodeRelease(record.Body)
   327  	if err != nil {
   328  		s.Log("failed to decode release %s: %v", key, err)
   329  		transaction.Rollback()
   330  		return nil, err
   331  	}
   332  	defer transaction.Commit()
   333  
   334  	_, err = transaction.Exec("DELETE FROM releases WHERE key = $1", key)
   335  	return release, err
   336  }