configstore: implement secret update

This commit is contained in:
Simone Gotti 2019-07-08 10:32:32 +02:00
parent 6508fd2b53
commit 289e691a58
4 changed files with 166 additions and 8 deletions

View File

@ -66,30 +66,38 @@ func (h *ActionHandler) GetSecrets(ctx context.Context, parentType types.ConfigT
return secrets, nil 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 == "" { 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) { 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 { 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 { switch secret.Type {
case types.SecretTypeInternal: case types.SecretTypeInternal:
if len(secret.Data) == 0 { 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 == "" { 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 == "" { 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 { 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 var cgt *datamanager.ChangeGroupsUpdateToken
@ -144,6 +152,86 @@ func (h *ActionHandler) CreateSecret(ctx context.Context, secret *types.Secret)
return secret, err 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 { func (h *ActionHandler) DeleteSecret(ctx context.Context, parentType types.ConfigType, parentRef, secretName string) error {
var secret *types.Secret var secret *types.Secret

View File

@ -226,6 +226,28 @@ func (c *Client) CreateProjectSecret(ctx context.Context, projectRef string, sec
return resSecret, resp, err 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) { 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) return c.getResponse(ctx, "DELETE", fmt.Sprintf("/projectgroups/%s/secrets/%s", url.PathEscape(projectGroupRef), secretName), nil, jsonContent, nil)
} }

View File

@ -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 { type DeleteSecretHandler struct {
log *zap.SugaredLogger log *zap.SugaredLogger
ah *action.ActionHandler ah *action.ActionHandler

View File

@ -134,6 +134,7 @@ func (s *Configstore) Run(ctx context.Context) error {
secretsHandler := api.NewSecretsHandler(logger, s.ah, s.readDB) secretsHandler := api.NewSecretsHandler(logger, s.ah, s.readDB)
createSecretHandler := api.NewCreateSecretHandler(logger, s.ah) createSecretHandler := api.NewCreateSecretHandler(logger, s.ah)
updateSecretHandler := api.NewUpdateSecretHandler(logger, s.ah)
deleteSecretHandler := api.NewDeleteSecretHandler(logger, s.ah) deleteSecretHandler := api.NewDeleteSecretHandler(logger, s.ah)
variablesHandler := api.NewVariablesHandler(logger, s.ah, s.readDB) 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("/projects/{projectref}/secrets", secretsHandler).Methods("GET")
apirouter.Handle("/projectgroups/{projectgroupref}/secrets", createSecretHandler).Methods("POST") apirouter.Handle("/projectgroups/{projectgroupref}/secrets", createSecretHandler).Methods("POST")
apirouter.Handle("/projects/{projectref}/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("/projectgroups/{projectgroupref}/secrets/{secretname}", deleteSecretHandler).Methods("DELETE")
apirouter.Handle("/projects/{projectref}/secrets/{secretname}", deleteSecretHandler).Methods("DELETE") apirouter.Handle("/projects/{projectref}/secrets/{secretname}", deleteSecretHandler).Methods("DELETE")