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 }