// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package static import ( "context" "fmt" "strings" "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/errors" "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/oplog" "github.com/hashicorp/go-dbw" ) // CreateCredentialStore inserts cs into the repository and returns a new // CredentialStore containing the credential store's PublicId. cs is not // changed. cs must not contain a PublicId. The PublicId is generated and // assigned by this method. cs must contain a valid ProjectId. // // Both cs.Name and cs.Description are optional. If cs.Name is set, it must // be unique within cs.ProjectId. Both cs.CreateTime and cs.UpdateTime are // ignored. func (r *Repository) CreateCredentialStore(ctx context.Context, cs *CredentialStore, _ ...Option) (*CredentialStore, error) { const op = "static.(Repository).CreateCredentialStore" if cs == nil { return nil, errors.New(ctx, errors.InvalidParameter, op, "missing CredentialStore") } if cs.CredentialStore == nil { return nil, errors.New(ctx, errors.InvalidParameter, op, "missing embedded CredentialStore") } if cs.ProjectId == "" { return nil, errors.New(ctx, errors.InvalidParameter, op, "missing project id") } if cs.PublicId != "" { return nil, errors.New(ctx, errors.InvalidParameter, op, "public id not empty") } cs = cs.clone() id, err := newCredentialStoreId(ctx) if err != nil { return nil, errors.Wrap(ctx, err, op) } cs.PublicId = id oplogWrapper, err := r.kms.GetWrapper(ctx, cs.ProjectId, kms.KeyPurposeOplog) if err != nil { return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) } var newCredentialStore *CredentialStore _, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, func(_ db.Reader, w db.Writer) error { newCredentialStore = cs.clone() if err := w.Create(ctx, newCredentialStore, db.WithOplog(oplogWrapper, newCredentialStore.oplog(oplog.OpType_OP_TYPE_CREATE))); err != nil { return errors.Wrap(ctx, err, op) } return nil }, ) if err != nil { if errors.IsUniqueError(err) { return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in project: %s: name %s already exists", cs.ProjectId, cs.Name))) } return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in project: %s", cs.ProjectId))) } return newCredentialStore, nil } // LookupCredentialStore returns the CredentialStore for publicId. Returns // nil, nil if no CredentialStore is found for publicId. func (r *Repository) LookupCredentialStore(ctx context.Context, publicId string, _ ...Option) (*CredentialStore, error) { const op = "static.(Repository).LookupCredentialStore" if publicId == "" { return nil, errors.New(ctx, errors.InvalidParameter, op, "no public id") } cs := allocCredentialStore() cs.PublicId = publicId if err := r.reader.LookupByPublicId(ctx, cs); err != nil { if errors.IsNotFoundError(err) { return nil, nil } return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for: %s", publicId))) } return cs, nil } // UpdateCredentialStore updates the repository entry for cs.PublicId with // the values in cs for the fields listed in fieldMaskPaths. It returns a // new CredentialStore containing the updated values and a count of the // number of records updated. cs is not changed. // // cs must contain a valid PublicId. Only Name and Description can be changed. If cs.Name // is set to a non-empty string, it must be unique within cs.ProjectId. // // An attribute of cs will be set to NULL in the database if the attribute // in cs is the zero value and it is included in fieldMaskPaths. func (r *Repository) UpdateCredentialStore(ctx context.Context, cs *CredentialStore, version uint32, fieldMaskPaths []string, _ ...Option) (*CredentialStore, int, error) { const op = "static.(Repository).UpdateCredentialStore" if cs == nil { return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing CredentialStore") } if cs.CredentialStore == nil { return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing embedded CredentialStore") } if cs.PublicId == "" { return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidPublicId, op, "missing public id") } if version == 0 { return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing version") } if cs.ProjectId == "" { return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing project id") } cs = cs.clone() for _, f := range fieldMaskPaths { switch { case strings.EqualFold(nameField, f): case strings.EqualFold(descriptionField, f): default: return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidFieldMask, op, f) } } dbMask, nullFields := dbw.BuildUpdatePaths( map[string]any{ nameField: cs.Name, descriptionField: cs.Description, }, fieldMaskPaths, nil, ) if len(dbMask) == 0 && len(nullFields) == 0 { return nil, db.NoRowsAffected, errors.New(ctx, errors.EmptyFieldMask, op, "missing field mask") } oplogWrapper, err := r.kms.GetWrapper(ctx, cs.ProjectId, kms.KeyPurposeOplog) if err != nil { return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) } var rowsUpdated int var returnedCredentialStore *CredentialStore _, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, func(_ db.Reader, w db.Writer) error { returnedCredentialStore = cs.clone() var err error rowsUpdated, err = w.Update(ctx, returnedCredentialStore, dbMask, nullFields, db.WithOplog(oplogWrapper, returnedCredentialStore.oplog(oplog.OpType_OP_TYPE_UPDATE)), db.WithVersion(&version)) if err != nil { return errors.Wrap(ctx, err, op) } if rowsUpdated > 1 { return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been updated") } return nil }, ) if err != nil { return nil, db.NoRowsAffected, err } return returnedCredentialStore, rowsUpdated, nil } // DeleteCredentialStore deletes publicId from the repository and returns // the number of records deleted. All options are ignored. func (r *Repository) DeleteCredentialStore(ctx context.Context, publicId string, _ ...Option) (int, error) { const op = "static.(Repository).DeleteCredentialStore" if publicId == "" { return db.NoRowsAffected, errors.New(ctx, errors.InvalidPublicId, op, "missing public id") } cs := allocCredentialStore() cs.PublicId = publicId if err := r.reader.LookupByPublicId(ctx, cs); err != nil { if errors.IsNotFoundError(err) { return db.NoRowsAffected, nil } return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", publicId))) } if cs.ProjectId == "" { return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "no project id") } oplogWrapper, err := r.kms.GetWrapper(ctx, cs.ProjectId, kms.KeyPurposeOplog) if err != nil { return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt), errors.WithMsg("unable to get oplog wrapper")) } var rowsDeleted int _, err = r.writer.DoTx( ctx, db.StdRetryCnt, db.ExpBackoff{}, func(_ db.Reader, w db.Writer) (err error) { rowsDeleted, err = w.Delete(ctx, cs, db.WithOplog(oplogWrapper, cs.oplog(oplog.OpType_OP_TYPE_DELETE))) if err != nil { return errors.Wrap(ctx, err, op) } if rowsDeleted > 1 { return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted") } return nil }, ) if err != nil { return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(publicId)) } return rowsDeleted, nil }