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 }