Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move setting endpoints to be under /settings/ #6649

Merged
merged 18 commits into from
Oct 1, 2023
Merged

Conversation

johnnyaug
Copy link
Contributor

@johnnyaug johnnyaug commented Sep 26, 2023

Resolves #6535.

  1. Deprecate existing endpoints for setting/getting branch protection rules and GC rules.
  2. Create new endpoints under /settings:
    • /settings/gc_rules: GET/PUT/DELETE all garbage collection rules.
    • /settings/branch_protect: GET/PUT all branch protection rules.

Notes

  1. Adding a single branch protection rule is no longer supported with the API. Instead, one should set all the rules at once. In order to avoid race conditions, a new ETag header is returned when getting the rules. The existing ETag should be specified as the If-Match header when setting the rules.

@github-actions
Copy link

github-actions bot commented Sep 26, 2023

♻️ PR Preview 91c95f3 has been successfully destroyed since this PR has been closed.

🤖 By surge-preview

@johnnyaug johnnyaug added the include-changelog PR description should be included in next release changelog label Sep 26, 2023
@johnnyaug johnnyaug changed the title 1.0/settings endpoints Move setting endpoints to be under `/settings/ Sep 26, 2023
@johnnyaug johnnyaug changed the title Move setting endpoints to be under `/settings/ Move setting endpoints to be under /settings/ Sep 26, 2023
@johnnyaug johnnyaug added area/API Improvements or additions to the API v1.0.0-blocker Issues that should be closed before going out with v1.0.0 and removed v1.0.0-blocker Issues that should be closed before going out with v1.0.0 labels Sep 26, 2023
@johnnyaug johnnyaug requested a review from guy-har September 26, 2023 15:38
@johnnyaug johnnyaug marked this pull request as ready for review September 26, 2023 20:58
@arielshaqed arielshaqed added the v1.0.0-blocker Issues that should be closed before going out with v1.0.0 label Sep 27, 2023
@johnnyaug johnnyaug requested a review from N-o-Z September 27, 2023 09:02
Copy link
Contributor

@guy-har guy-har left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice and clean 😄
Added some comments, didn't go over tests and controller yet

@@ -4077,8 +4201,8 @@ paths:

post:
tags:
- retention
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not leave retention and add internal? same goes for other removed tags

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tags are reflected in the method names in the SDK, we want them to appear as "internal" to emphasize the fact that they are deprecated. These endpoints will be removed in the future.

err = json.NewDecoder(reader).Decode(&body)
if err != nil {
DieErr(err)
}
client := getClient()
resp, err := client.SetGarbageCollectionRulesWithResponse(cmd.Context(), u.Repository, body)
resp, err := client.SetGCRules(cmd.Context(), u.Repository, body)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With response

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed here, I think?

Comment on lines -88 to -90
if errors.Is(err, graveler.ErrNotFound) {
return &graveler.BranchProtectionRules{}, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was removed, and seems good, just checking it was intentional

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional: decided to propagate the error to the controller, and handle it there.

}
if err != nil {
return nil, err
func (m *ProtectionManager) SetRules(ctx context.Context, repository *graveler.RepositoryRecord, rules *graveler.BranchProtectionRules, ifMatchETag *string) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func (m *ProtectionManager) SetRules(ctx context.Context, repository *graveler.RepositoryRecord, rules *graveler.BranchProtectionRules, ifMatchETag *string) error {
func (m *ProtectionManager) SetRules(ctx context.Context, repository *graveler.RepositoryRecord, rules *graveler.BranchProtectionRules, etagPredicate *string) error {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to lastKnownChecksum. Didn't want to reuse the "predicate" word, it's not really a predicate

Comment on lines 616 to 617
// If ifMatchETag is not nil, the update will only succeed if the current ETag matches the given one.
// Otherwise, ErrPreconditionFailed is returned.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And if ifMatchETag? no validation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added clarification and changed the behavior in some of the levels.

@@ -67,6 +68,21 @@ func (m *Manager) Save(ctx context.Context, repository *graveler.RepositoryRecor
return kv.SetMsg(ctx, m.store, graveler.RepoPartition(repository), []byte(graveler.SettingsPath(key)), setting)
}

// SaveIf persists the given setting under the given repository and key. Overrides settings key in KV Store.
// The setting is persisted only if the given ETag matches the ETag of the current setting.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, I think we should document what happens ifMatchETag is nil

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -4059,8 +4183,8 @@ paths:
type: string
get:
tags:
- retention
operationId: getGarbageCollectionRules
- internal
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with the new settings path, are the old ones internal or deprecated?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are deprecated: marking them as internal was a way to make sure users know they are deprecated. I will also add the deprecated notation like you did in your recent PR.

const response = await apiRequest(`/repositories/${encodeURIComponent(repoID)}/settings/branch_protection`, {
method: 'PUT',
body: JSON.stringify(rules),
}, {'ETag': lastKnownChecksum});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure we want the name to be ETag, it's basically an implementation detail, for the user it's just a lock/token/predicate or something like that

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not care much either way: ETag is by now a term of HTTP, not only of S3. It's an actual standard header.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ETag is part of the HTTP spec and is intended to be used together with the If-Match header.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UPDATE: this was a mistake, the header used here should be If-Match.

Copy link
Contributor

@arielshaqed arielshaqed left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Nothing major except my confusion around ETags and the if-match stuff, sorry.

resp, err := client.GetBranchProtectionRulesWithResponse(cmd.Context(), u.Repository)
DieOnErrorOrUnexpectedStatusCode(resp, err, http.StatusOK)
rules := *resp.JSON200
for i, rule := range *resp.JSON200 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, thanks!

Comment on lines 1754 to 1758
if eTag == "" {
// workaround since swagger doesn't allow empty headers
// https://github.com/deepmap/oapi-codegen/issues/954
eTag = base64.StdEncoding.EncodeToString([]byte("EMPTY"))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is quite worrying. Can we omit the etag if this happens? What if we used openapi-codegen version 6.6.0, or 7.0.0? @Jonathan-Rosenberg & I need to bump at least to 6.6.0 and probably to 7.0.0.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed this.

Comment on lines 1786 to 1791
eTag := params.IfMatch
if swag.StringValue(eTag) == "" {
// workaround since swagger doesn't allow empty headers
// https://github.com/deepmap/oapi-codegen/issues/954
eTag = swag.String(base64.StdEncoding.EncodeToString([]byte("EMPTY")))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this. If there is no If-Match header, I think this makes it behave as though we did ask for a particular ETag.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, this is not needed.

@@ -2113,7 +2241,9 @@ func (c *Controller) handleAPIErrorCallback(ctx context.Context, w http.Response
case errors.Is(err, graveler.ErrTooManyTries):
log.Debug("Retried too many times")
cb(w, r, http.StatusLocked, "Too many attempts, try again later")

case errors.Is(err, graveler.ErrPreconditionFailed):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What changed that we need this now? Or is it a quick fix for a bug that you discovered along the way?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error is now returned from Graveler when the conditional update of branch protection rules fails.

const response = await apiRequest(`/repositories/${encodeURIComponent(repoID)}/settings/branch_protection`, {
method: 'PUT',
body: JSON.stringify(rules),
}, {'ETag': lastKnownChecksum});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not care much either way: ETag is by now a term of HTTP, not only of S3. It's an actual standard header.

@@ -2113,7 +2241,9 @@ func (c *Controller) handleAPIErrorCallback(ctx context.Context, w http.Response
case errors.Is(err, graveler.ErrTooManyTries):
log.Debug("Retried too many times")
cb(w, r, http.StatusLocked, "Too many attempts, try again later")

case errors.Is(err, graveler.ErrPreconditionFailed):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏽

@@ -67,6 +68,21 @@ func (m *Manager) Save(ctx context.Context, repository *graveler.RepositoryRecor
return kv.SetMsg(ctx, m.store, graveler.RepoPartition(repository), []byte(graveler.SettingsPath(key)), setting)
}

// SaveIf persists the given setting under the given repository and key. Overrides settings key in KV Store.
// The setting is persisted only if the given ETag matches the ETag of the current setting.
// Otherwise, ErrPreconditionFailed is returned.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually in our code when predicate (ifMatchETag) is nil - this is an indication for saveIf not exists. This might cause a bit of confusion

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, will this make the Save method redundant?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed (kind of): changed this to no longer be a pointer. The update is always conditional - empty string means update-if-not-exists. Now branch.ProtectionManager exposes both SetRules and SetRulesIf. SetRules uses the unconditional Save.

However, the catalog and graveler still use a pointer, where nil means unconditional update, and empty string means update-if-not-exists.

esti/ugc_test.go Outdated
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did we remove this?
We are removing test coverage without providing an alternative

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that these are not Spark tests - they test the lakeFS API

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is no longer used (it tests code that doesn't exist). It is replaced by "unified GC tests".

return
}
writeResponse(w, r, http.StatusNoContent, nil)
func (c *Controller) InternalSetGarbageCollectionRules(w http.ResponseWriter, r *http.Request, body apigen.InternalSetGarbageCollectionRulesJSONRequestBody, repository string) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain why we need the Internal functions

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are keeping them for backward compatibility, and they will be removed in the future.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I see what you did there - the api endpoint didn't change

@@ -610,15 +610,12 @@ type VersionController interface {
GCNewRunID() string

// GetBranchProtectionRules return all branch protection rules for the repository
GetBranchProtectionRules(ctx context.Context, repository *RepositoryRecord) (*BranchProtectionRules, error)
GetBranchProtectionRules(ctx context.Context, repository *RepositoryRecord) (*BranchProtectionRules, string, error)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you update documentation on the returned values.
Also why is the etag necessary on the branch protection rules but not in GC rules?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Contributor

@arielshaqed arielshaqed left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@johnnyaug johnnyaug merged commit 2676ff3 into master Oct 1, 2023
34 checks passed
@johnnyaug johnnyaug deleted the 1.0/settings_endpoints branch October 1, 2023 10:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/API Improvements or additions to the API include-changelog PR description should be included in next release changelog v1.0.0-blocker Issues that should be closed before going out with v1.0.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

API cleanup: consolidate repository level properties/metadata
4 participants