configstore: implement secret update
This commit is contained in:
parent
6508fd2b53
commit
289e691a58
|
@ -66,30 +66,38 @@ func (h *ActionHandler) GetSecrets(ctx context.Context, parentType types.ConfigT
|
|||
return secrets, nil
|
||||
}
|
||||
|
||||
func (h *ActionHandler) CreateSecret(ctx context.Context, secret *types.Secret) (*types.Secret, error) {
|
||||
func (h *ActionHandler) ValidateSecret(ctx context.Context, secret *types.Secret) error {
|
||||
if secret.Name == "" {
|
||||
return nil, util.NewErrBadRequest(errors.Errorf("secret name required"))
|
||||
return util.NewErrBadRequest(errors.Errorf("secret name required"))
|
||||
}
|
||||
if !util.ValidateName(secret.Name) {
|
||||
return nil, util.NewErrBadRequest(errors.Errorf("invalid secret name %q", secret.Name))
|
||||
return util.NewErrBadRequest(errors.Errorf("invalid secret name %q", secret.Name))
|
||||
}
|
||||
if secret.Type != types.SecretTypeInternal {
|
||||
return nil, util.NewErrBadRequest(errors.Errorf("invalid secret type %q", secret.Type))
|
||||
return util.NewErrBadRequest(errors.Errorf("invalid secret type %q", secret.Type))
|
||||
}
|
||||
switch secret.Type {
|
||||
case types.SecretTypeInternal:
|
||||
if len(secret.Data) == 0 {
|
||||
return nil, util.NewErrBadRequest(errors.Errorf("empty secret data"))
|
||||
return util.NewErrBadRequest(errors.Errorf("empty secret data"))
|
||||
}
|
||||
}
|
||||
if secret.Parent.Type == "" {
|
||||
return nil, util.NewErrBadRequest(errors.Errorf("secret parent type required"))
|
||||
return util.NewErrBadRequest(errors.Errorf("secret parent type required"))
|
||||
}
|
||||
if secret.Parent.ID == "" {
|
||||
return nil, util.NewErrBadRequest(errors.Errorf("secret parentid required"))
|
||||
return util.NewErrBadRequest(errors.Errorf("secret parentid required"))
|
||||
}
|
||||
if secret.Parent.Type != types.ConfigTypeProject && secret.Parent.Type != types.ConfigTypeProjectGroup {
|
||||
return nil, util.NewErrBadRequest(errors.Errorf("invalid secret parent type %q", secret.Parent.Type))
|
||||
return util.NewErrBadRequest(errors.Errorf("invalid secret parent type %q", secret.Parent.Type))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *ActionHandler) CreateSecret(ctx context.Context, secret *types.Secret) (*types.Secret, error) {
|
||||
if err := h.ValidateSecret(ctx, secret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var cgt *datamanager.ChangeGroupsUpdateToken
|
||||
|
@ -144,6 +152,86 @@ func (h *ActionHandler) CreateSecret(ctx context.Context, secret *types.Secret)
|
|||
return secret, err
|
||||
}
|
||||
|
||||
type UpdateSecretRequest struct {
|
||||
SecretName string
|
||||
|
||||
Secret *types.Secret
|
||||
}
|
||||
|
||||
func (h *ActionHandler) UpdateSecret(ctx context.Context, req *UpdateSecretRequest) (*types.Secret, error) {
|
||||
if err := h.ValidateSecret(ctx, req.Secret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var curSecret *types.Secret
|
||||
var cgt *datamanager.ChangeGroupsUpdateToken
|
||||
// changegroup is the secret name
|
||||
|
||||
// must do all the checks in a single transaction to avoid concurrent changes
|
||||
err := h.readDB.Do(func(tx *db.Tx) error {
|
||||
var err error
|
||||
|
||||
parentID, err := h.readDB.ResolveConfigID(tx, req.Secret.Parent.Type, req.Secret.Parent.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Secret.Parent.ID = parentID
|
||||
|
||||
// check secret exists
|
||||
curSecret, err = h.readDB.GetSecretByName(tx, req.Secret.Parent.ID, req.SecretName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if curSecret == nil {
|
||||
return util.NewErrBadRequest(errors.Errorf("secret with name %q for %s with id %q doesn't exists", req.SecretName, req.Secret.Parent.Type, req.Secret.Parent.ID))
|
||||
}
|
||||
|
||||
if curSecret.Name != req.Secret.Name {
|
||||
// check duplicate secret name
|
||||
u, err := h.readDB.GetSecretByName(tx, req.Secret.Parent.ID, req.Secret.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if u != nil {
|
||||
return util.NewErrBadRequest(errors.Errorf("secret with name %q for %s with id %q already exists", req.Secret.Name, req.Secret.Parent.Type, req.Secret.Parent.ID))
|
||||
}
|
||||
}
|
||||
|
||||
// set/override ID that must be kept from the current secret
|
||||
req.Secret.ID = curSecret.ID
|
||||
|
||||
cgNames := []string{
|
||||
util.EncodeSha256Hex("secretname-" + req.Secret.ID),
|
||||
util.EncodeSha256Hex("secretname-" + req.Secret.Name),
|
||||
}
|
||||
cgt, err = h.readDB.GetChangeGroupsUpdateTokens(tx, cgNames)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
secretj, err := json.Marshal(req.Secret)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("failed to marshal secret: %w", err)
|
||||
}
|
||||
actions := []*datamanager.Action{
|
||||
{
|
||||
ActionType: datamanager.ActionTypePut,
|
||||
DataType: string(types.ConfigTypeSecret),
|
||||
ID: req.Secret.ID,
|
||||
Data: secretj,
|
||||
},
|
||||
}
|
||||
|
||||
_, err = h.dm.WriteWal(ctx, actions, cgt)
|
||||
return req.Secret, err
|
||||
}
|
||||
|
||||
func (h *ActionHandler) DeleteSecret(ctx context.Context, parentType types.ConfigType, parentRef, secretName string) error {
|
||||
var secret *types.Secret
|
||||
|
||||
|
|
|
@ -226,6 +226,28 @@ func (c *Client) CreateProjectSecret(ctx context.Context, projectRef string, sec
|
|||
return resSecret, resp, err
|
||||
}
|
||||
|
||||
func (c *Client) UpdateProjectGroupSecret(ctx context.Context, projectGroupRef, secretName string, secret *types.Secret) (*Secret, *http.Response, error) {
|
||||
pj, err := json.Marshal(secret)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
resSecret := new(Secret)
|
||||
resp, err := c.getParsedResponse(ctx, "PUT", fmt.Sprintf("/projectgroups/%s/secrets/%s", url.PathEscape(projectGroupRef), secretName), nil, jsonContent, bytes.NewReader(pj), resSecret)
|
||||
return resSecret, resp, err
|
||||
}
|
||||
|
||||
func (c *Client) UpdateProjectSecret(ctx context.Context, projectRef, secretName string, secret *types.Secret) (*Secret, *http.Response, error) {
|
||||
pj, err := json.Marshal(secret)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
resSecret := new(Secret)
|
||||
resp, err := c.getParsedResponse(ctx, "PUT", fmt.Sprintf("/projects/%s/secrets/%s", url.PathEscape(projectRef), secretName), nil, jsonContent, bytes.NewReader(pj), resSecret)
|
||||
return resSecret, resp, err
|
||||
}
|
||||
|
||||
func (c *Client) DeleteProjectGroupSecret(ctx context.Context, projectGroupRef, secretName string) (*http.Response, error) {
|
||||
return c.getResponse(ctx, "DELETE", fmt.Sprintf("/projectgroups/%s/secrets/%s", url.PathEscape(projectGroupRef), secretName), nil, jsonContent, nil)
|
||||
}
|
||||
|
|
|
@ -154,6 +154,51 @@ func (h *CreateSecretHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
|
|||
}
|
||||
}
|
||||
|
||||
type UpdateSecretHandler struct {
|
||||
log *zap.SugaredLogger
|
||||
ah *action.ActionHandler
|
||||
}
|
||||
|
||||
func NewUpdateSecretHandler(logger *zap.Logger, ah *action.ActionHandler) *UpdateSecretHandler {
|
||||
return &UpdateSecretHandler{log: logger.Sugar(), ah: ah}
|
||||
}
|
||||
|
||||
func (h *UpdateSecretHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
vars := mux.Vars(r)
|
||||
secretName := vars["secretname"]
|
||||
|
||||
parentType, parentRef, err := GetConfigTypeRef(r)
|
||||
if httpError(w, err) {
|
||||
h.log.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
var secret *types.Secret
|
||||
d := json.NewDecoder(r.Body)
|
||||
if err := d.Decode(&secret); err != nil {
|
||||
httpError(w, util.NewErrBadRequest(err))
|
||||
return
|
||||
}
|
||||
|
||||
secret.Parent.Type = parentType
|
||||
secret.Parent.ID = parentRef
|
||||
|
||||
areq := &action.UpdateSecretRequest{
|
||||
SecretName: secretName,
|
||||
Secret: secret,
|
||||
}
|
||||
secret, err = h.ah.UpdateSecret(ctx, areq)
|
||||
if httpError(w, err) {
|
||||
h.log.Errorf("err: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := httpResponse(w, http.StatusOK, secret); err != nil {
|
||||
h.log.Errorf("err: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
type DeleteSecretHandler struct {
|
||||
log *zap.SugaredLogger
|
||||
ah *action.ActionHandler
|
||||
|
|
|
@ -134,6 +134,7 @@ func (s *Configstore) Run(ctx context.Context) error {
|
|||
|
||||
secretsHandler := api.NewSecretsHandler(logger, s.ah, s.readDB)
|
||||
createSecretHandler := api.NewCreateSecretHandler(logger, s.ah)
|
||||
updateSecretHandler := api.NewUpdateSecretHandler(logger, s.ah)
|
||||
deleteSecretHandler := api.NewDeleteSecretHandler(logger, s.ah)
|
||||
|
||||
variablesHandler := api.NewVariablesHandler(logger, s.ah, s.readDB)
|
||||
|
@ -190,6 +191,8 @@ func (s *Configstore) Run(ctx context.Context) error {
|
|||
apirouter.Handle("/projects/{projectref}/secrets", secretsHandler).Methods("GET")
|
||||
apirouter.Handle("/projectgroups/{projectgroupref}/secrets", createSecretHandler).Methods("POST")
|
||||
apirouter.Handle("/projects/{projectref}/secrets", createSecretHandler).Methods("POST")
|
||||
apirouter.Handle("/projectgroups/{projectgroupref}/secrets/{secretname}", updateSecretHandler).Methods("PUT")
|
||||
apirouter.Handle("/projects/{projectref}/secrets/{secretname}", updateSecretHandler).Methods("PUT")
|
||||
apirouter.Handle("/projectgroups/{projectgroupref}/secrets/{secretname}", deleteSecretHandler).Methods("DELETE")
|
||||
apirouter.Handle("/projects/{projectref}/secrets/{secretname}", deleteSecretHandler).Methods("DELETE")
|
||||
|
||||
|
|
Loading…
Reference in New Issue