From d37c9bcbfefa32dfd03a4cc6d32cdae40b3e816b Mon Sep 17 00:00:00 2001 From: "Ariel Shaqed (Scolnicov)" Date: Thu, 11 Jan 2024 12:50:52 +0200 Subject: [PATCH] Add "hard reset" operation (#7263) * Add "hard reset" operation * Add Esti test for hard reset * make gen * Separate "hard reset" API from regular ("soft") reset API The 2 APIs share almost no parameters. While Git gets away with using the same verb for several different "reset" operations, this is very confusing in OpenAPI. * [CR] Use Graveler branch utilities to hard-reset, check if dirty first * [bug] Validate Ref type Validation reports compile-time type errors at run-time ;-(. * [bug] Reset to "branch~" Was tpyoed to "branch/", which is obviously wrong. * Move API to "experimental" and clean up Swagger leftovers * Use "expected, actual" style in require.Equal in test * [bug] Return created metarange ID on Commit Caused strange failures like [this one](https://github.com/treeverse/lakeFS/actions/runs/7486726066/job/20377896805?pr=7263#step:4:7210) --- api/swagger.yml | 50 ++++ clients/java-legacy/README.md | 1 + clients/java-legacy/api/openapi.yaml | 76 +++++++ clients/java-legacy/docs/ExperimentalApi.md | 102 +++++++++ clients/java-legacy/docs/ResetCreation.md | 2 +- .../lakefs/clients/api/ExperimentalApi.java | 163 +++++++++++++ .../clients/api/model/ResetCreation.java | 6 +- .../clients/api/ExperimentalApiTest.java | 18 ++ clients/java/README.md | 1 + clients/java/api/openapi.yaml | 76 +++++++ clients/java/docs/ExperimentalApi.md | 104 +++++++++ clients/java/docs/ResetCreation.md | 2 +- .../lakefs/clients/sdk/ExperimentalApi.java | 214 ++++++++++++++++++ .../clients/sdk/model/ResetCreation.java | 4 +- .../clients/sdk/ExperimentalApiTest.java | 19 ++ clients/python-legacy/README.md | 1 + clients/python-legacy/docs/ExperimentalApi.md | 125 ++++++++++ clients/python-legacy/docs/ResetCreation.md | 2 +- .../lakefs_client/api/experimental_api.py | 147 ++++++++++++ .../lakefs_client/model/reset_creation.py | 4 +- .../test/test_experimental_api.py | 7 + clients/python/README.md | 1 + clients/python/docs/ExperimentalApi.md | 118 ++++++++++ clients/python/docs/ResetCreation.md | 2 +- .../python/lakefs_sdk/api/experimental_api.py | 163 ++++++++++++- .../lakefs_sdk/models/reset_creation.py | 2 +- clients/python/test/test_experimental_api.py | 7 + docs/assets/js/swagger.yml | 50 ++++ esti/rollback_test.go | 52 ++++- pkg/api/controller.go | 23 ++ pkg/catalog/catalog.go | 18 ++ pkg/graveler/graveler.go | 67 ++++++ pkg/graveler/mock/graveler.go | 19 ++ 33 files changed, 1629 insertions(+), 17 deletions(-) diff --git a/api/swagger.yml b/api/swagger.yml index 831095ebb24..27a67c22070 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -482,6 +482,7 @@ components: type: type: string enum: [object, common_prefix, reset] + description: What to reset according to path. path: type: string force: @@ -3379,6 +3380,55 @@ paths: default: $ref: "#/components/responses/ServerError" + /repositories/{repository}/branches/{branch}/hard_reset: + parameters: + - in: path + name: repository + required: true + schema: + type: string + - in: path + name: branch + required: true + schema: + type: string + put: + tags: + - experimental + operationId: hardResetBranch + summary: hard reset branch + description: + Relocate branch to refer to ref. Branch must not contain + uncommitted data. + parameters: + - in: query + name: ref + required: true + schema: + type: string + description: After reset, branch will point at this reference. + - in: query + name: force + required: false + schema: + type: boolean + default: false + responses: + 204: + description: reset successful + 400: + $ref: "#/components/responses/BadRequest" + 401: + $ref: "#/components/responses/Unauthorized" + 403: + $ref: "#/components/responses/Forbidden" + 404: + $ref: "#/components/responses/NotFound" + 420: + description: too many requests + default: + $ref: "#/components/responses/ServerError" + /repositories/{repository}/branches/{branch}/revert: parameters: - in: path diff --git a/clients/java-legacy/README.md b/clients/java-legacy/README.md index 79ae8f64478..cb0da3ea02f 100644 --- a/clients/java-legacy/README.md +++ b/clients/java-legacy/README.md @@ -185,6 +185,7 @@ Class | Method | HTTP request | Description *ExperimentalApi* | [**completePresignMultipartUpload**](docs/ExperimentalApi.md#completePresignMultipartUpload) | **PUT** /repositories/{repository}/branches/{branch}/staging/pmpu/{uploadId} | Complete a presign multipart upload request *ExperimentalApi* | [**createPresignMultipartUpload**](docs/ExperimentalApi.md#createPresignMultipartUpload) | **POST** /repositories/{repository}/branches/{branch}/staging/pmpu | Initiate a multipart upload *ExperimentalApi* | [**getOtfDiffs**](docs/ExperimentalApi.md#getOtfDiffs) | **GET** /otf/diffs | get the available Open Table Format diffs +*ExperimentalApi* | [**hardResetBranch**](docs/ExperimentalApi.md#hardResetBranch) | **PUT** /repositories/{repository}/branches/{branch}/hard_reset | hard reset branch *ExperimentalApi* | [**otfDiff**](docs/ExperimentalApi.md#otfDiff) | **GET** /repositories/{repository}/otf/refs/{left_ref}/diff/{right_ref} | perform otf diff *HealthCheckApi* | [**healthCheck**](docs/HealthCheckApi.md#healthCheck) | **GET** /healthcheck | *ImportApi* | [**importCancel**](docs/ImportApi.md#importCancel) | **DELETE** /repositories/{repository}/branches/{branch}/import | cancel ongoing import diff --git a/clients/java-legacy/api/openapi.yaml b/clients/java-legacy/api/openapi.yaml index 9486211a18a..56ca8d0353b 100644 --- a/clients/java-legacy/api/openapi.yaml +++ b/clients/java-legacy/api/openapi.yaml @@ -3282,6 +3282,81 @@ paths: - branches x-contentType: application/json x-accepts: application/json + /repositories/{repository}/branches/{branch}/hard_reset: + put: + description: Relocate branch to refer to ref. Branch must not contain uncommitted + data. + operationId: hardResetBranch + parameters: + - explode: false + in: path + name: repository + required: true + schema: + type: string + style: simple + - explode: false + in: path + name: branch + required: true + schema: + type: string + style: simple + - description: After reset, branch will point at this reference. + explode: true + in: query + name: ref + required: true + schema: + type: string + style: form + - explode: true + in: query + name: force + required: false + schema: + default: false + type: boolean + style: form + responses: + "204": + description: reset successful + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Bad Request + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Forbidden + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Resource Not Found + "420": + description: too many requests + default: + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Internal Server Error + summary: hard reset branch + tags: + - experimental + x-accepts: application/json /repositories/{repository}/branches/{branch}/revert: post: operationId: revertBranch @@ -6942,6 +7017,7 @@ components: type: object properties: type: + description: What to reset according to path. enum: - object - common_prefix diff --git a/clients/java-legacy/docs/ExperimentalApi.md b/clients/java-legacy/docs/ExperimentalApi.md index b3fcd350830..29430c3e10b 100644 --- a/clients/java-legacy/docs/ExperimentalApi.md +++ b/clients/java-legacy/docs/ExperimentalApi.md @@ -8,6 +8,7 @@ Method | HTTP request | Description [**completePresignMultipartUpload**](ExperimentalApi.md#completePresignMultipartUpload) | **PUT** /repositories/{repository}/branches/{branch}/staging/pmpu/{uploadId} | Complete a presign multipart upload request [**createPresignMultipartUpload**](ExperimentalApi.md#createPresignMultipartUpload) | **POST** /repositories/{repository}/branches/{branch}/staging/pmpu | Initiate a multipart upload [**getOtfDiffs**](ExperimentalApi.md#getOtfDiffs) | **GET** /otf/diffs | get the available Open Table Format diffs +[**hardResetBranch**](ExperimentalApi.md#hardResetBranch) | **PUT** /repositories/{repository}/branches/{branch}/hard_reset | hard reset branch [**otfDiff**](ExperimentalApi.md#otfDiff) | **GET** /repositories/{repository}/otf/refs/{left_ref}/diff/{right_ref} | perform otf diff @@ -405,6 +406,107 @@ This endpoint does not need any parameter. **420** | too many requests | - | **0** | Internal Server Error | - | + +# **hardResetBranch** +> hardResetBranch(repository, branch, ref, force) + +hard reset branch + +Relocate branch to refer to ref. Branch must not contain uncommitted data. + +### Example +```java +// Import classes: +import io.lakefs.clients.api.ApiClient; +import io.lakefs.clients.api.ApiException; +import io.lakefs.clients.api.Configuration; +import io.lakefs.clients.api.auth.*; +import io.lakefs.clients.api.models.*; +import io.lakefs.clients.api.ExperimentalApi; + +public class Example { + public static void main(String[] args) { + ApiClient defaultClient = Configuration.getDefaultApiClient(); + defaultClient.setBasePath("http://localhost/api/v1"); + + // Configure HTTP basic authorization: basic_auth + HttpBasicAuth basic_auth = (HttpBasicAuth) defaultClient.getAuthentication("basic_auth"); + basic_auth.setUsername("YOUR USERNAME"); + basic_auth.setPassword("YOUR PASSWORD"); + + // Configure API key authorization: cookie_auth + ApiKeyAuth cookie_auth = (ApiKeyAuth) defaultClient.getAuthentication("cookie_auth"); + cookie_auth.setApiKey("YOUR API KEY"); + // Uncomment the following line to set a prefix for the API key, e.g. "Token" (defaults to null) + //cookie_auth.setApiKeyPrefix("Token"); + + // Configure HTTP bearer authorization: jwt_token + HttpBearerAuth jwt_token = (HttpBearerAuth) defaultClient.getAuthentication("jwt_token"); + jwt_token.setBearerToken("BEARER TOKEN"); + + // Configure API key authorization: oidc_auth + ApiKeyAuth oidc_auth = (ApiKeyAuth) defaultClient.getAuthentication("oidc_auth"); + oidc_auth.setApiKey("YOUR API KEY"); + // Uncomment the following line to set a prefix for the API key, e.g. "Token" (defaults to null) + //oidc_auth.setApiKeyPrefix("Token"); + + // Configure API key authorization: saml_auth + ApiKeyAuth saml_auth = (ApiKeyAuth) defaultClient.getAuthentication("saml_auth"); + saml_auth.setApiKey("YOUR API KEY"); + // Uncomment the following line to set a prefix for the API key, e.g. "Token" (defaults to null) + //saml_auth.setApiKeyPrefix("Token"); + + ExperimentalApi apiInstance = new ExperimentalApi(defaultClient); + String repository = "repository_example"; // String | + String branch = "branch_example"; // String | + String ref = "ref_example"; // String | After reset, branch will point at this reference. + Boolean force = false; // Boolean | + try { + apiInstance.hardResetBranch(repository, branch, ref, force); + } catch (ApiException e) { + System.err.println("Exception when calling ExperimentalApi#hardResetBranch"); + System.err.println("Status code: " + e.getCode()); + System.err.println("Reason: " + e.getResponseBody()); + System.err.println("Response headers: " + e.getResponseHeaders()); + e.printStackTrace(); + } + } +} +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **repository** | **String**| | + **branch** | **String**| | + **ref** | **String**| After reset, branch will point at this reference. | + **force** | **Boolean**| | [optional] [default to false] + +### Return type + +null (empty response body) + +### Authorization + +[basic_auth](../README.md#basic_auth), [cookie_auth](../README.md#cookie_auth), [jwt_token](../README.md#jwt_token), [oidc_auth](../README.md#oidc_auth), [saml_auth](../README.md#saml_auth) + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +**204** | reset successful | - | +**400** | Bad Request | - | +**401** | Unauthorized | - | +**403** | Forbidden | - | +**404** | Resource Not Found | - | +**420** | too many requests | - | +**0** | Internal Server Error | - | + # **otfDiff** > OtfDiffList otfDiff(repository, leftRef, rightRef, tablePath, type) diff --git a/clients/java-legacy/docs/ResetCreation.md b/clients/java-legacy/docs/ResetCreation.md index 53857ce01fd..32b69fa7f8c 100644 --- a/clients/java-legacy/docs/ResetCreation.md +++ b/clients/java-legacy/docs/ResetCreation.md @@ -7,7 +7,7 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**type** | [**TypeEnum**](#TypeEnum) | | +**type** | [**TypeEnum**](#TypeEnum) | What to reset according to path. | **path** | **String** | | [optional] **force** | **Boolean** | | [optional] diff --git a/clients/java-legacy/src/main/java/io/lakefs/clients/api/ExperimentalApi.java b/clients/java-legacy/src/main/java/io/lakefs/clients/api/ExperimentalApi.java index ea80045e123..02b38ec1f56 100644 --- a/clients/java-legacy/src/main/java/io/lakefs/clients/api/ExperimentalApi.java +++ b/clients/java-legacy/src/main/java/io/lakefs/clients/api/ExperimentalApi.java @@ -676,6 +676,169 @@ public okhttp3.Call getOtfDiffsAsync(final ApiCallback _callback) thro localVarApiClient.executeAsync(localVarCall, localVarReturnType, _callback); return localVarCall; } + /** + * Build call for hardResetBranch + * @param repository (required) + * @param branch (required) + * @param ref After reset, branch will point at this reference. (required) + * @param force (optional, default to false) + * @param _callback Callback for upload/download progress + * @return Call to execute + * @throws ApiException If fail to serialize the request body object + * @http.response.details + + + + + + + + + +
Status Code Description Response Headers
204 reset successful -
400 Bad Request -
401 Unauthorized -
403 Forbidden -
404 Resource Not Found -
420 too many requests -
0 Internal Server Error -
+ */ + public okhttp3.Call hardResetBranchCall(String repository, String branch, String ref, Boolean force, final ApiCallback _callback) throws ApiException { + Object localVarPostBody = null; + + // create path and map variables + String localVarPath = "/repositories/{repository}/branches/{branch}/hard_reset" + .replaceAll("\\{" + "repository" + "\\}", localVarApiClient.escapeString(repository.toString())) + .replaceAll("\\{" + "branch" + "\\}", localVarApiClient.escapeString(branch.toString())); + + List localVarQueryParams = new ArrayList(); + List localVarCollectionQueryParams = new ArrayList(); + Map localVarHeaderParams = new HashMap(); + Map localVarCookieParams = new HashMap(); + Map localVarFormParams = new HashMap(); + + if (ref != null) { + localVarQueryParams.addAll(localVarApiClient.parameterToPair("ref", ref)); + } + + if (force != null) { + localVarQueryParams.addAll(localVarApiClient.parameterToPair("force", force)); + } + + final String[] localVarAccepts = { + "application/json" + }; + final String localVarAccept = localVarApiClient.selectHeaderAccept(localVarAccepts); + if (localVarAccept != null) { + localVarHeaderParams.put("Accept", localVarAccept); + } + + final String[] localVarContentTypes = { + + }; + final String localVarContentType = localVarApiClient.selectHeaderContentType(localVarContentTypes); + localVarHeaderParams.put("Content-Type", localVarContentType); + + String[] localVarAuthNames = new String[] { "basic_auth", "cookie_auth", "jwt_token", "oidc_auth", "saml_auth" }; + return localVarApiClient.buildCall(localVarPath, "PUT", localVarQueryParams, localVarCollectionQueryParams, localVarPostBody, localVarHeaderParams, localVarCookieParams, localVarFormParams, localVarAuthNames, _callback); + } + + @SuppressWarnings("rawtypes") + private okhttp3.Call hardResetBranchValidateBeforeCall(String repository, String branch, String ref, Boolean force, final ApiCallback _callback) throws ApiException { + + // verify the required parameter 'repository' is set + if (repository == null) { + throw new ApiException("Missing the required parameter 'repository' when calling hardResetBranch(Async)"); + } + + // verify the required parameter 'branch' is set + if (branch == null) { + throw new ApiException("Missing the required parameter 'branch' when calling hardResetBranch(Async)"); + } + + // verify the required parameter 'ref' is set + if (ref == null) { + throw new ApiException("Missing the required parameter 'ref' when calling hardResetBranch(Async)"); + } + + + okhttp3.Call localVarCall = hardResetBranchCall(repository, branch, ref, force, _callback); + return localVarCall; + + } + + /** + * hard reset branch + * Relocate branch to refer to ref. Branch must not contain uncommitted data. + * @param repository (required) + * @param branch (required) + * @param ref After reset, branch will point at this reference. (required) + * @param force (optional, default to false) + * @throws ApiException If fail to call the API, e.g. server error or cannot deserialize the response body + * @http.response.details + + + + + + + + + +
Status Code Description Response Headers
204 reset successful -
400 Bad Request -
401 Unauthorized -
403 Forbidden -
404 Resource Not Found -
420 too many requests -
0 Internal Server Error -
+ */ + public void hardResetBranch(String repository, String branch, String ref, Boolean force) throws ApiException { + hardResetBranchWithHttpInfo(repository, branch, ref, force); + } + + /** + * hard reset branch + * Relocate branch to refer to ref. Branch must not contain uncommitted data. + * @param repository (required) + * @param branch (required) + * @param ref After reset, branch will point at this reference. (required) + * @param force (optional, default to false) + * @return ApiResponse<Void> + * @throws ApiException If fail to call the API, e.g. server error or cannot deserialize the response body + * @http.response.details + + + + + + + + + +
Status Code Description Response Headers
204 reset successful -
400 Bad Request -
401 Unauthorized -
403 Forbidden -
404 Resource Not Found -
420 too many requests -
0 Internal Server Error -
+ */ + public ApiResponse hardResetBranchWithHttpInfo(String repository, String branch, String ref, Boolean force) throws ApiException { + okhttp3.Call localVarCall = hardResetBranchValidateBeforeCall(repository, branch, ref, force, null); + return localVarApiClient.execute(localVarCall); + } + + /** + * hard reset branch (asynchronously) + * Relocate branch to refer to ref. Branch must not contain uncommitted data. + * @param repository (required) + * @param branch (required) + * @param ref After reset, branch will point at this reference. (required) + * @param force (optional, default to false) + * @param _callback The callback to be executed when the API call finishes + * @return The request call + * @throws ApiException If fail to process the API call, e.g. serializing the request body object + * @http.response.details + + + + + + + + + +
Status Code Description Response Headers
204 reset successful -
400 Bad Request -
401 Unauthorized -
403 Forbidden -
404 Resource Not Found -
420 too many requests -
0 Internal Server Error -
+ */ + public okhttp3.Call hardResetBranchAsync(String repository, String branch, String ref, Boolean force, final ApiCallback _callback) throws ApiException { + + okhttp3.Call localVarCall = hardResetBranchValidateBeforeCall(repository, branch, ref, force, _callback); + localVarApiClient.executeAsync(localVarCall, _callback); + return localVarCall; + } /** * Build call for otfDiff * @param repository (required) diff --git a/clients/java-legacy/src/main/java/io/lakefs/clients/api/model/ResetCreation.java b/clients/java-legacy/src/main/java/io/lakefs/clients/api/model/ResetCreation.java index 43cc00aa3c4..19e5eac4cf4 100644 --- a/clients/java-legacy/src/main/java/io/lakefs/clients/api/model/ResetCreation.java +++ b/clients/java-legacy/src/main/java/io/lakefs/clients/api/model/ResetCreation.java @@ -30,7 +30,7 @@ @javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen") public class ResetCreation { /** - * Gets or Sets type + * What to reset according to path. */ @JsonAdapter(TypeEnum.Adapter.class) public enum TypeEnum { @@ -98,11 +98,11 @@ public ResetCreation type(TypeEnum type) { } /** - * Get type + * What to reset according to path. * @return type **/ @javax.annotation.Nonnull - @ApiModelProperty(required = true, value = "") + @ApiModelProperty(required = true, value = "What to reset according to path.") public TypeEnum getType() { return type; diff --git a/clients/java-legacy/src/test/java/io/lakefs/clients/api/ExperimentalApiTest.java b/clients/java-legacy/src/test/java/io/lakefs/clients/api/ExperimentalApiTest.java index a9275822642..f71ca0fa013 100644 --- a/clients/java-legacy/src/test/java/io/lakefs/clients/api/ExperimentalApiTest.java +++ b/clients/java-legacy/src/test/java/io/lakefs/clients/api/ExperimentalApiTest.java @@ -109,6 +109,24 @@ public void getOtfDiffsTest() throws ApiException { // TODO: test validations } + /** + * hard reset branch + * + * Relocate branch to refer to ref. Branch must not contain uncommitted data. + * + * @throws ApiException + * if the Api call fails + */ + @Test + public void hardResetBranchTest() throws ApiException { + String repository = null; + String branch = null; + String ref = null; + Boolean force = null; + api.hardResetBranch(repository, branch, ref, force); + // TODO: test validations + } + /** * perform otf diff * diff --git a/clients/java/README.md b/clients/java/README.md index 25ab5bc4190..ff48f2bd74d 100644 --- a/clients/java/README.md +++ b/clients/java/README.md @@ -193,6 +193,7 @@ Class | Method | HTTP request | Description *ExperimentalApi* | [**completePresignMultipartUpload**](docs/ExperimentalApi.md#completePresignMultipartUpload) | **PUT** /repositories/{repository}/branches/{branch}/staging/pmpu/{uploadId} | Complete a presign multipart upload request *ExperimentalApi* | [**createPresignMultipartUpload**](docs/ExperimentalApi.md#createPresignMultipartUpload) | **POST** /repositories/{repository}/branches/{branch}/staging/pmpu | Initiate a multipart upload *ExperimentalApi* | [**getOtfDiffs**](docs/ExperimentalApi.md#getOtfDiffs) | **GET** /otf/diffs | get the available Open Table Format diffs +*ExperimentalApi* | [**hardResetBranch**](docs/ExperimentalApi.md#hardResetBranch) | **PUT** /repositories/{repository}/branches/{branch}/hard_reset | hard reset branch *ExperimentalApi* | [**otfDiff**](docs/ExperimentalApi.md#otfDiff) | **GET** /repositories/{repository}/otf/refs/{left_ref}/diff/{right_ref} | perform otf diff *HealthCheckApi* | [**healthCheck**](docs/HealthCheckApi.md#healthCheck) | **GET** /healthcheck | *ImportApi* | [**importCancel**](docs/ImportApi.md#importCancel) | **DELETE** /repositories/{repository}/branches/{branch}/import | cancel ongoing import diff --git a/clients/java/api/openapi.yaml b/clients/java/api/openapi.yaml index 30e9c979670..3cfa2072a5c 100644 --- a/clients/java/api/openapi.yaml +++ b/clients/java/api/openapi.yaml @@ -3282,6 +3282,81 @@ paths: - branches x-content-type: application/json x-accepts: application/json + /repositories/{repository}/branches/{branch}/hard_reset: + put: + description: Relocate branch to refer to ref. Branch must not contain uncommitted + data. + operationId: hardResetBranch + parameters: + - explode: false + in: path + name: repository + required: true + schema: + type: string + style: simple + - explode: false + in: path + name: branch + required: true + schema: + type: string + style: simple + - description: "After reset, branch will point at this reference." + explode: true + in: query + name: ref + required: true + schema: + type: string + style: form + - explode: true + in: query + name: force + required: false + schema: + default: false + type: boolean + style: form + responses: + "204": + description: reset successful + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Bad Request + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Unauthorized + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Forbidden + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Resource Not Found + "420": + description: too many requests + default: + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Internal Server Error + summary: hard reset branch + tags: + - experimental + x-accepts: application/json /repositories/{repository}/branches/{branch}/revert: post: operationId: revertBranch @@ -6916,6 +6991,7 @@ components: type: object properties: type: + description: What to reset according to path. enum: - object - common_prefix diff --git a/clients/java/docs/ExperimentalApi.md b/clients/java/docs/ExperimentalApi.md index 9bf00b026e0..5840aad26a9 100644 --- a/clients/java/docs/ExperimentalApi.md +++ b/clients/java/docs/ExperimentalApi.md @@ -8,6 +8,7 @@ All URIs are relative to */api/v1* | [**completePresignMultipartUpload**](ExperimentalApi.md#completePresignMultipartUpload) | **PUT** /repositories/{repository}/branches/{branch}/staging/pmpu/{uploadId} | Complete a presign multipart upload request | | [**createPresignMultipartUpload**](ExperimentalApi.md#createPresignMultipartUpload) | **POST** /repositories/{repository}/branches/{branch}/staging/pmpu | Initiate a multipart upload | | [**getOtfDiffs**](ExperimentalApi.md#getOtfDiffs) | **GET** /otf/diffs | get the available Open Table Format diffs | +| [**hardResetBranch**](ExperimentalApi.md#hardResetBranch) | **PUT** /repositories/{repository}/branches/{branch}/hard_reset | hard reset branch | | [**otfDiff**](ExperimentalApi.md#otfDiff) | **GET** /repositories/{repository}/otf/refs/{left_ref}/diff/{right_ref} | perform otf diff | @@ -412,6 +413,109 @@ This endpoint does not need any parameter. | **420** | too many requests | - | | **0** | Internal Server Error | - | + +# **hardResetBranch** +> hardResetBranch(repository, branch, ref).force(force).execute(); + +hard reset branch + +Relocate branch to refer to ref. Branch must not contain uncommitted data. + +### Example +```java +// Import classes: +import io.lakefs.clients.sdk.ApiClient; +import io.lakefs.clients.sdk.ApiException; +import io.lakefs.clients.sdk.Configuration; +import io.lakefs.clients.sdk.auth.*; +import io.lakefs.clients.sdk.models.*; +import io.lakefs.clients.sdk.ExperimentalApi; + +public class Example { + public static void main(String[] args) { + ApiClient defaultClient = Configuration.getDefaultApiClient(); + defaultClient.setBasePath("/api/v1"); + + // Configure HTTP basic authorization: basic_auth + HttpBasicAuth basic_auth = (HttpBasicAuth) defaultClient.getAuthentication("basic_auth"); + basic_auth.setUsername("YOUR USERNAME"); + basic_auth.setPassword("YOUR PASSWORD"); + + // Configure API key authorization: cookie_auth + ApiKeyAuth cookie_auth = (ApiKeyAuth) defaultClient.getAuthentication("cookie_auth"); + cookie_auth.setApiKey("YOUR API KEY"); + // Uncomment the following line to set a prefix for the API key, e.g. "Token" (defaults to null) + //cookie_auth.setApiKeyPrefix("Token"); + + // Configure API key authorization: oidc_auth + ApiKeyAuth oidc_auth = (ApiKeyAuth) defaultClient.getAuthentication("oidc_auth"); + oidc_auth.setApiKey("YOUR API KEY"); + // Uncomment the following line to set a prefix for the API key, e.g. "Token" (defaults to null) + //oidc_auth.setApiKeyPrefix("Token"); + + // Configure API key authorization: saml_auth + ApiKeyAuth saml_auth = (ApiKeyAuth) defaultClient.getAuthentication("saml_auth"); + saml_auth.setApiKey("YOUR API KEY"); + // Uncomment the following line to set a prefix for the API key, e.g. "Token" (defaults to null) + //saml_auth.setApiKeyPrefix("Token"); + + // Configure HTTP bearer authorization: jwt_token + HttpBearerAuth jwt_token = (HttpBearerAuth) defaultClient.getAuthentication("jwt_token"); + jwt_token.setBearerToken("BEARER TOKEN"); + + ExperimentalApi apiInstance = new ExperimentalApi(defaultClient); + String repository = "repository_example"; // String | + String branch = "branch_example"; // String | + String ref = "ref_example"; // String | After reset, branch will point at this reference. + Boolean force = false; // Boolean | + try { + apiInstance.hardResetBranch(repository, branch, ref) + .force(force) + .execute(); + } catch (ApiException e) { + System.err.println("Exception when calling ExperimentalApi#hardResetBranch"); + System.err.println("Status code: " + e.getCode()); + System.err.println("Reason: " + e.getResponseBody()); + System.err.println("Response headers: " + e.getResponseHeaders()); + e.printStackTrace(); + } + } +} +``` + +### Parameters + +| Name | Type | Description | Notes | +|------------- | ------------- | ------------- | -------------| +| **repository** | **String**| | | +| **branch** | **String**| | | +| **ref** | **String**| After reset, branch will point at this reference. | | +| **force** | **Boolean**| | [optional] [default to false] | + +### Return type + +null (empty response body) + +### Authorization + +[basic_auth](../README.md#basic_auth), [cookie_auth](../README.md#cookie_auth), [oidc_auth](../README.md#oidc_auth), [saml_auth](../README.md#saml_auth), [jwt_token](../README.md#jwt_token) + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +| **204** | reset successful | - | +| **400** | Bad Request | - | +| **401** | Unauthorized | - | +| **403** | Forbidden | - | +| **404** | Resource Not Found | - | +| **420** | too many requests | - | +| **0** | Internal Server Error | - | + # **otfDiff** > OtfDiffList otfDiff(repository, leftRef, rightRef, tablePath, type).execute(); diff --git a/clients/java/docs/ResetCreation.md b/clients/java/docs/ResetCreation.md index d121923aa52..1fc3f50e42c 100644 --- a/clients/java/docs/ResetCreation.md +++ b/clients/java/docs/ResetCreation.md @@ -7,7 +7,7 @@ | Name | Type | Description | Notes | |------------ | ------------- | ------------- | -------------| -|**type** | [**TypeEnum**](#TypeEnum) | | | +|**type** | [**TypeEnum**](#TypeEnum) | What to reset according to path. | | |**path** | **String** | | [optional] | |**force** | **Boolean** | | [optional] | diff --git a/clients/java/src/main/java/io/lakefs/clients/sdk/ExperimentalApi.java b/clients/java/src/main/java/io/lakefs/clients/sdk/ExperimentalApi.java index c4bbbc57bc6..058e66539e5 100644 --- a/clients/java/src/main/java/io/lakefs/clients/sdk/ExperimentalApi.java +++ b/clients/java/src/main/java/io/lakefs/clients/sdk/ExperimentalApi.java @@ -889,6 +889,220 @@ public okhttp3.Call executeAsync(final ApiCallback _callback) throws A public APIgetOtfDiffsRequest getOtfDiffs() { return new APIgetOtfDiffsRequest(); } + private okhttp3.Call hardResetBranchCall(String repository, String branch, String ref, Boolean force, final ApiCallback _callback) throws ApiException { + String basePath = null; + // Operation Servers + String[] localBasePaths = new String[] { }; + + // Determine Base Path to Use + if (localCustomBaseUrl != null){ + basePath = localCustomBaseUrl; + } else if ( localBasePaths.length > 0 ) { + basePath = localBasePaths[localHostIndex]; + } else { + basePath = null; + } + + Object localVarPostBody = null; + + // create path and map variables + String localVarPath = "/repositories/{repository}/branches/{branch}/hard_reset" + .replace("{" + "repository" + "}", localVarApiClient.escapeString(repository.toString())) + .replace("{" + "branch" + "}", localVarApiClient.escapeString(branch.toString())); + + List localVarQueryParams = new ArrayList(); + List localVarCollectionQueryParams = new ArrayList(); + Map localVarHeaderParams = new HashMap(); + Map localVarCookieParams = new HashMap(); + Map localVarFormParams = new HashMap(); + + if (ref != null) { + localVarQueryParams.addAll(localVarApiClient.parameterToPair("ref", ref)); + } + + if (force != null) { + localVarQueryParams.addAll(localVarApiClient.parameterToPair("force", force)); + } + + final String[] localVarAccepts = { + "application/json" + }; + final String localVarAccept = localVarApiClient.selectHeaderAccept(localVarAccepts); + if (localVarAccept != null) { + localVarHeaderParams.put("Accept", localVarAccept); + } + + final String[] localVarContentTypes = { + }; + final String localVarContentType = localVarApiClient.selectHeaderContentType(localVarContentTypes); + if (localVarContentType != null) { + localVarHeaderParams.put("Content-Type", localVarContentType); + } + + String[] localVarAuthNames = new String[] { "basic_auth", "cookie_auth", "oidc_auth", "saml_auth", "jwt_token" }; + return localVarApiClient.buildCall(basePath, localVarPath, "PUT", localVarQueryParams, localVarCollectionQueryParams, localVarPostBody, localVarHeaderParams, localVarCookieParams, localVarFormParams, localVarAuthNames, _callback); + } + + @SuppressWarnings("rawtypes") + private okhttp3.Call hardResetBranchValidateBeforeCall(String repository, String branch, String ref, Boolean force, final ApiCallback _callback) throws ApiException { + // verify the required parameter 'repository' is set + if (repository == null) { + throw new ApiException("Missing the required parameter 'repository' when calling hardResetBranch(Async)"); + } + + // verify the required parameter 'branch' is set + if (branch == null) { + throw new ApiException("Missing the required parameter 'branch' when calling hardResetBranch(Async)"); + } + + // verify the required parameter 'ref' is set + if (ref == null) { + throw new ApiException("Missing the required parameter 'ref' when calling hardResetBranch(Async)"); + } + + return hardResetBranchCall(repository, branch, ref, force, _callback); + + } + + + private ApiResponse hardResetBranchWithHttpInfo(String repository, String branch, String ref, Boolean force) throws ApiException { + okhttp3.Call localVarCall = hardResetBranchValidateBeforeCall(repository, branch, ref, force, null); + return localVarApiClient.execute(localVarCall); + } + + private okhttp3.Call hardResetBranchAsync(String repository, String branch, String ref, Boolean force, final ApiCallback _callback) throws ApiException { + + okhttp3.Call localVarCall = hardResetBranchValidateBeforeCall(repository, branch, ref, force, _callback); + localVarApiClient.executeAsync(localVarCall, _callback); + return localVarCall; + } + + public class APIhardResetBranchRequest { + private final String repository; + private final String branch; + private final String ref; + private Boolean force; + + private APIhardResetBranchRequest(String repository, String branch, String ref) { + this.repository = repository; + this.branch = branch; + this.ref = ref; + } + + /** + * Set force + * @param force (optional, default to false) + * @return APIhardResetBranchRequest + */ + public APIhardResetBranchRequest force(Boolean force) { + this.force = force; + return this; + } + + /** + * Build call for hardResetBranch + * @param _callback ApiCallback API callback + * @return Call to execute + * @throws ApiException If fail to serialize the request body object + * @http.response.details + + + + + + + + + +
Status Code Description Response Headers
204 reset successful -
400 Bad Request -
401 Unauthorized -
403 Forbidden -
404 Resource Not Found -
420 too many requests -
0 Internal Server Error -
+ */ + public okhttp3.Call buildCall(final ApiCallback _callback) throws ApiException { + return hardResetBranchCall(repository, branch, ref, force, _callback); + } + + /** + * Execute hardResetBranch request + * @throws ApiException If fail to call the API, e.g. server error or cannot deserialize the response body + * @http.response.details + + + + + + + + + +
Status Code Description Response Headers
204 reset successful -
400 Bad Request -
401 Unauthorized -
403 Forbidden -
404 Resource Not Found -
420 too many requests -
0 Internal Server Error -
+ */ + public void execute() throws ApiException { + hardResetBranchWithHttpInfo(repository, branch, ref, force); + } + + /** + * Execute hardResetBranch request with HTTP info returned + * @return ApiResponse<Void> + * @throws ApiException If fail to call the API, e.g. server error or cannot deserialize the response body + * @http.response.details + + + + + + + + + +
Status Code Description Response Headers
204 reset successful -
400 Bad Request -
401 Unauthorized -
403 Forbidden -
404 Resource Not Found -
420 too many requests -
0 Internal Server Error -
+ */ + public ApiResponse executeWithHttpInfo() throws ApiException { + return hardResetBranchWithHttpInfo(repository, branch, ref, force); + } + + /** + * Execute hardResetBranch request (asynchronously) + * @param _callback The callback to be executed when the API call finishes + * @return The request call + * @throws ApiException If fail to process the API call, e.g. serializing the request body object + * @http.response.details + + + + + + + + + +
Status Code Description Response Headers
204 reset successful -
400 Bad Request -
401 Unauthorized -
403 Forbidden -
404 Resource Not Found -
420 too many requests -
0 Internal Server Error -
+ */ + public okhttp3.Call executeAsync(final ApiCallback _callback) throws ApiException { + return hardResetBranchAsync(repository, branch, ref, force, _callback); + } + } + + /** + * hard reset branch + * Relocate branch to refer to ref. Branch must not contain uncommitted data. + * @param repository (required) + * @param branch (required) + * @param ref After reset, branch will point at this reference. (required) + * @return APIhardResetBranchRequest + * @http.response.details + + + + + + + + + +
Status Code Description Response Headers
204 reset successful -
400 Bad Request -
401 Unauthorized -
403 Forbidden -
404 Resource Not Found -
420 too many requests -
0 Internal Server Error -
+ */ + public APIhardResetBranchRequest hardResetBranch(String repository, String branch, String ref) { + return new APIhardResetBranchRequest(repository, branch, ref); + } private okhttp3.Call otfDiffCall(String repository, String leftRef, String rightRef, String tablePath, String type, final ApiCallback _callback) throws ApiException { String basePath = null; // Operation Servers diff --git a/clients/java/src/main/java/io/lakefs/clients/sdk/model/ResetCreation.java b/clients/java/src/main/java/io/lakefs/clients/sdk/model/ResetCreation.java index f318c3f3f13..914adfb53bb 100644 --- a/clients/java/src/main/java/io/lakefs/clients/sdk/model/ResetCreation.java +++ b/clients/java/src/main/java/io/lakefs/clients/sdk/model/ResetCreation.java @@ -53,7 +53,7 @@ @javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen") public class ResetCreation { /** - * Gets or Sets type + * What to reset according to path. */ @JsonAdapter(TypeEnum.Adapter.class) public enum TypeEnum { @@ -123,7 +123,7 @@ public ResetCreation type(TypeEnum type) { } /** - * Get type + * What to reset according to path. * @return type **/ @javax.annotation.Nonnull diff --git a/clients/java/src/test/java/io/lakefs/clients/sdk/ExperimentalApiTest.java b/clients/java/src/test/java/io/lakefs/clients/sdk/ExperimentalApiTest.java index 16a635c0daa..aed361ff813 100644 --- a/clients/java/src/test/java/io/lakefs/clients/sdk/ExperimentalApiTest.java +++ b/clients/java/src/test/java/io/lakefs/clients/sdk/ExperimentalApiTest.java @@ -109,6 +109,25 @@ public void getOtfDiffsTest() throws ApiException { // TODO: test validations } + /** + * hard reset branch + * + * Relocate branch to refer to ref. Branch must not contain uncommitted data. + * + * @throws ApiException if the Api call fails + */ + @Test + public void hardResetBranchTest() throws ApiException { + String repository = null; + String branch = null; + String ref = null; + Boolean force = null; + api.hardResetBranch(repository, branch, ref) + .force(force) + .execute(); + // TODO: test validations + } + /** * perform otf diff * diff --git a/clients/python-legacy/README.md b/clients/python-legacy/README.md index 24372958d3c..6c7ca0bc220 100644 --- a/clients/python-legacy/README.md +++ b/clients/python-legacy/README.md @@ -166,6 +166,7 @@ Class | Method | HTTP request | Description *ExperimentalApi* | [**complete_presign_multipart_upload**](docs/ExperimentalApi.md#complete_presign_multipart_upload) | **PUT** /repositories/{repository}/branches/{branch}/staging/pmpu/{uploadId} | Complete a presign multipart upload request *ExperimentalApi* | [**create_presign_multipart_upload**](docs/ExperimentalApi.md#create_presign_multipart_upload) | **POST** /repositories/{repository}/branches/{branch}/staging/pmpu | Initiate a multipart upload *ExperimentalApi* | [**get_otf_diffs**](docs/ExperimentalApi.md#get_otf_diffs) | **GET** /otf/diffs | get the available Open Table Format diffs +*ExperimentalApi* | [**hard_reset_branch**](docs/ExperimentalApi.md#hard_reset_branch) | **PUT** /repositories/{repository}/branches/{branch}/hard_reset | hard reset branch *ExperimentalApi* | [**otf_diff**](docs/ExperimentalApi.md#otf_diff) | **GET** /repositories/{repository}/otf/refs/{left_ref}/diff/{right_ref} | perform otf diff *HealthCheckApi* | [**health_check**](docs/HealthCheckApi.md#health_check) | **GET** /healthcheck | *ImportApi* | [**import_cancel**](docs/ImportApi.md#import_cancel) | **DELETE** /repositories/{repository}/branches/{branch}/import | cancel ongoing import diff --git a/clients/python-legacy/docs/ExperimentalApi.md b/clients/python-legacy/docs/ExperimentalApi.md index 2b3e1958ef2..e92f566935e 100644 --- a/clients/python-legacy/docs/ExperimentalApi.md +++ b/clients/python-legacy/docs/ExperimentalApi.md @@ -8,6 +8,7 @@ Method | HTTP request | Description [**complete_presign_multipart_upload**](ExperimentalApi.md#complete_presign_multipart_upload) | **PUT** /repositories/{repository}/branches/{branch}/staging/pmpu/{uploadId} | Complete a presign multipart upload request [**create_presign_multipart_upload**](ExperimentalApi.md#create_presign_multipart_upload) | **POST** /repositories/{repository}/branches/{branch}/staging/pmpu | Initiate a multipart upload [**get_otf_diffs**](ExperimentalApi.md#get_otf_diffs) | **GET** /otf/diffs | get the available Open Table Format diffs +[**hard_reset_branch**](ExperimentalApi.md#hard_reset_branch) | **PUT** /repositories/{repository}/branches/{branch}/hard_reset | hard reset branch [**otf_diff**](ExperimentalApi.md#otf_diff) | **GET** /repositories/{repository}/otf/refs/{left_ref}/diff/{right_ref} | perform otf diff @@ -511,6 +512,130 @@ This endpoint does not need any parameter. [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) +# **hard_reset_branch** +> hard_reset_branch(repository, branch, ref) + +hard reset branch + +Relocate branch to refer to ref. Branch must not contain uncommitted data. + +### Example + +* Basic Authentication (basic_auth): +* Api Key Authentication (cookie_auth): +* Bearer (JWT) Authentication (jwt_token): +* Api Key Authentication (oidc_auth): +* Api Key Authentication (saml_auth): + +```python +import time +import lakefs_client +from lakefs_client.api import experimental_api +from lakefs_client.model.error import Error +from pprint import pprint +# Defining the host is optional and defaults to http://localhost/api/v1 +# See configuration.py for a list of all supported configuration parameters. +configuration = lakefs_client.Configuration( + host = "http://localhost/api/v1" +) + +# The client must configure the authentication and authorization parameters +# in accordance with the API server security policy. +# Examples for each auth method are provided below, use the example that +# satisfies your auth use case. + +# Configure HTTP basic authorization: basic_auth +configuration = lakefs_client.Configuration( + username = 'YOUR_USERNAME', + password = 'YOUR_PASSWORD' +) + +# Configure API key authorization: cookie_auth +configuration.api_key['cookie_auth'] = 'YOUR_API_KEY' + +# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed +# configuration.api_key_prefix['cookie_auth'] = 'Bearer' + +# Configure Bearer authorization (JWT): jwt_token +configuration = lakefs_client.Configuration( + access_token = 'YOUR_BEARER_TOKEN' +) + +# Configure API key authorization: oidc_auth +configuration.api_key['oidc_auth'] = 'YOUR_API_KEY' + +# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed +# configuration.api_key_prefix['oidc_auth'] = 'Bearer' + +# Configure API key authorization: saml_auth +configuration.api_key['saml_auth'] = 'YOUR_API_KEY' + +# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed +# configuration.api_key_prefix['saml_auth'] = 'Bearer' + +# Enter a context with an instance of the API client +with lakefs_client.ApiClient(configuration) as api_client: + # Create an instance of the API class + api_instance = experimental_api.ExperimentalApi(api_client) + repository = "repository_example" # str | + branch = "branch_example" # str | + ref = "ref_example" # str | After reset, branch will point at this reference. + force = False # bool | (optional) if omitted the server will use the default value of False + + # example passing only required values which don't have defaults set + try: + # hard reset branch + api_instance.hard_reset_branch(repository, branch, ref) + except lakefs_client.ApiException as e: + print("Exception when calling ExperimentalApi->hard_reset_branch: %s\n" % e) + + # example passing only required values which don't have defaults set + # and optional values + try: + # hard reset branch + api_instance.hard_reset_branch(repository, branch, ref, force=force) + except lakefs_client.ApiException as e: + print("Exception when calling ExperimentalApi->hard_reset_branch: %s\n" % e) +``` + + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **repository** | **str**| | + **branch** | **str**| | + **ref** | **str**| After reset, branch will point at this reference. | + **force** | **bool**| | [optional] if omitted the server will use the default value of False + +### Return type + +void (empty response body) + +### Authorization + +[basic_auth](../README.md#basic_auth), [cookie_auth](../README.md#cookie_auth), [jwt_token](../README.md#jwt_token), [oidc_auth](../README.md#oidc_auth), [saml_auth](../README.md#saml_auth) + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + + +### HTTP response details + +| Status code | Description | Response headers | +|-------------|-------------|------------------| +**204** | reset successful | - | +**400** | Bad Request | - | +**401** | Unauthorized | - | +**403** | Forbidden | - | +**404** | Resource Not Found | - | +**420** | too many requests | - | +**0** | Internal Server Error | - | + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + # **otf_diff** > OtfDiffList otf_diff(repository, left_ref, right_ref, table_path, type) diff --git a/clients/python-legacy/docs/ResetCreation.md b/clients/python-legacy/docs/ResetCreation.md index 3293ce15517..cc21688295d 100644 --- a/clients/python-legacy/docs/ResetCreation.md +++ b/clients/python-legacy/docs/ResetCreation.md @@ -4,7 +4,7 @@ ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**type** | **str** | | +**type** | **str** | What to reset according to path. | **path** | **str** | | [optional] **force** | **bool** | | [optional] if omitted the server will use the default value of False **any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional] diff --git a/clients/python-legacy/lakefs_client/api/experimental_api.py b/clients/python-legacy/lakefs_client/api/experimental_api.py index bf3516c8dd1..5a53cd286dc 100644 --- a/clients/python-legacy/lakefs_client/api/experimental_api.py +++ b/clients/python-legacy/lakefs_client/api/experimental_api.py @@ -321,6 +321,78 @@ def __init__(self, api_client=None): }, api_client=api_client ) + self.hard_reset_branch_endpoint = _Endpoint( + settings={ + 'response_type': None, + 'auth': [ + 'basic_auth', + 'cookie_auth', + 'jwt_token', + 'oidc_auth', + 'saml_auth' + ], + 'endpoint_path': '/repositories/{repository}/branches/{branch}/hard_reset', + 'operation_id': 'hard_reset_branch', + 'http_method': 'PUT', + 'servers': None, + }, + params_map={ + 'all': [ + 'repository', + 'branch', + 'ref', + 'force', + ], + 'required': [ + 'repository', + 'branch', + 'ref', + ], + 'nullable': [ + ], + 'enum': [ + ], + 'validation': [ + ] + }, + root_map={ + 'validations': { + }, + 'allowed_values': { + }, + 'openapi_types': { + 'repository': + (str,), + 'branch': + (str,), + 'ref': + (str,), + 'force': + (bool,), + }, + 'attribute_map': { + 'repository': 'repository', + 'branch': 'branch', + 'ref': 'ref', + 'force': 'force', + }, + 'location_map': { + 'repository': 'path', + 'branch': 'path', + 'ref': 'query', + 'force': 'query', + }, + 'collection_format_map': { + } + }, + headers_map={ + 'accept': [ + 'application/json' + ], + 'content_type': [], + }, + api_client=api_client + ) self.otf_diff_endpoint = _Endpoint( settings={ 'response_type': (OtfDiffList,), @@ -694,6 +766,81 @@ def get_otf_diffs( kwargs['_host_index'] = kwargs.get('_host_index') return self.get_otf_diffs_endpoint.call_with_http_info(**kwargs) + def hard_reset_branch( + self, + repository, + branch, + ref, + **kwargs + ): + """hard reset branch # noqa: E501 + + Relocate branch to refer to ref. Branch must not contain uncommitted data. # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + + >>> thread = api.hard_reset_branch(repository, branch, ref, async_req=True) + >>> result = thread.get() + + Args: + repository (str): + branch (str): + ref (str): After reset, branch will point at this reference. + + Keyword Args: + force (bool): [optional] if omitted the server will use the default value of False + _return_http_data_only (bool): response data without head status + code and headers. Default is True. + _preload_content (bool): if False, the urllib3.HTTPResponse object + will be returned without reading/decoding response data. + Default is True. + _request_timeout (int/float/tuple): timeout setting for this request. If + one number provided, it will be total request timeout. It can also + be a pair (tuple) of (connection, read) timeouts. + Default is None. + _check_input_type (bool): specifies if type checking + should be done one the data sent to the server. + Default is True. + _check_return_type (bool): specifies if type checking + should be done one the data received from the server. + Default is True. + _host_index (int/None): specifies the index of the server + that we want to use. + Default is read from the configuration. + async_req (bool): execute request asynchronously + + Returns: + None + If the method is called asynchronously, returns the request + thread. + """ + kwargs['async_req'] = kwargs.get( + 'async_req', False + ) + kwargs['_return_http_data_only'] = kwargs.get( + '_return_http_data_only', True + ) + kwargs['_preload_content'] = kwargs.get( + '_preload_content', True + ) + kwargs['_request_timeout'] = kwargs.get( + '_request_timeout', None + ) + kwargs['_check_input_type'] = kwargs.get( + '_check_input_type', True + ) + kwargs['_check_return_type'] = kwargs.get( + '_check_return_type', True + ) + kwargs['_host_index'] = kwargs.get('_host_index') + kwargs['repository'] = \ + repository + kwargs['branch'] = \ + branch + kwargs['ref'] = \ + ref + return self.hard_reset_branch_endpoint.call_with_http_info(**kwargs) + def otf_diff( self, repository, diff --git a/clients/python-legacy/lakefs_client/model/reset_creation.py b/clients/python-legacy/lakefs_client/model/reset_creation.py index 7d70af2643b..f664d96d743 100644 --- a/clients/python-legacy/lakefs_client/model/reset_creation.py +++ b/clients/python-legacy/lakefs_client/model/reset_creation.py @@ -114,7 +114,7 @@ def _from_openapi_data(cls, type, *args, **kwargs): # noqa: E501 """ResetCreation - a model defined in OpenAPI Args: - type (str): + type (str): What to reset according to path. Keyword Args: _check_type (bool): if True, values for parameters in openapi_types @@ -201,7 +201,7 @@ def __init__(self, type, *args, **kwargs): # noqa: E501 """ResetCreation - a model defined in OpenAPI Args: - type (str): + type (str): What to reset according to path. Keyword Args: _check_type (bool): if True, values for parameters in openapi_types diff --git a/clients/python-legacy/test/test_experimental_api.py b/clients/python-legacy/test/test_experimental_api.py index 8989b319218..95a1968fdb0 100644 --- a/clients/python-legacy/test/test_experimental_api.py +++ b/clients/python-legacy/test/test_experimental_api.py @@ -52,6 +52,13 @@ def test_get_otf_diffs(self): """ pass + def test_hard_reset_branch(self): + """Test case for hard_reset_branch + + hard reset branch # noqa: E501 + """ + pass + def test_otf_diff(self): """Test case for otf_diff diff --git a/clients/python/README.md b/clients/python/README.md index 70758bd67e6..8dab5920e33 100644 --- a/clients/python/README.md +++ b/clients/python/README.md @@ -169,6 +169,7 @@ Class | Method | HTTP request | Description *ExperimentalApi* | [**complete_presign_multipart_upload**](docs/ExperimentalApi.md#complete_presign_multipart_upload) | **PUT** /repositories/{repository}/branches/{branch}/staging/pmpu/{uploadId} | Complete a presign multipart upload request *ExperimentalApi* | [**create_presign_multipart_upload**](docs/ExperimentalApi.md#create_presign_multipart_upload) | **POST** /repositories/{repository}/branches/{branch}/staging/pmpu | Initiate a multipart upload *ExperimentalApi* | [**get_otf_diffs**](docs/ExperimentalApi.md#get_otf_diffs) | **GET** /otf/diffs | get the available Open Table Format diffs +*ExperimentalApi* | [**hard_reset_branch**](docs/ExperimentalApi.md#hard_reset_branch) | **PUT** /repositories/{repository}/branches/{branch}/hard_reset | hard reset branch *ExperimentalApi* | [**otf_diff**](docs/ExperimentalApi.md#otf_diff) | **GET** /repositories/{repository}/otf/refs/{left_ref}/diff/{right_ref} | perform otf diff *HealthCheckApi* | [**health_check**](docs/HealthCheckApi.md#health_check) | **GET** /healthcheck | *ImportApi* | [**import_cancel**](docs/ImportApi.md#import_cancel) | **DELETE** /repositories/{repository}/branches/{branch}/import | cancel ongoing import diff --git a/clients/python/docs/ExperimentalApi.md b/clients/python/docs/ExperimentalApi.md index fb1c14f66de..6751693c210 100644 --- a/clients/python/docs/ExperimentalApi.md +++ b/clients/python/docs/ExperimentalApi.md @@ -8,6 +8,7 @@ Method | HTTP request | Description [**complete_presign_multipart_upload**](ExperimentalApi.md#complete_presign_multipart_upload) | **PUT** /repositories/{repository}/branches/{branch}/staging/pmpu/{uploadId} | Complete a presign multipart upload request [**create_presign_multipart_upload**](ExperimentalApi.md#create_presign_multipart_upload) | **POST** /repositories/{repository}/branches/{branch}/staging/pmpu | Initiate a multipart upload [**get_otf_diffs**](ExperimentalApi.md#get_otf_diffs) | **GET** /otf/diffs | get the available Open Table Format diffs +[**hard_reset_branch**](ExperimentalApi.md#hard_reset_branch) | **PUT** /repositories/{repository}/branches/{branch}/hard_reset | hard reset branch [**otf_diff**](ExperimentalApi.md#otf_diff) | **GET** /repositories/{repository}/otf/refs/{left_ref}/diff/{right_ref} | perform otf diff @@ -477,6 +478,123 @@ This endpoint does not need any parameter. [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) +# **hard_reset_branch** +> hard_reset_branch(repository, branch, ref, force=force) + +hard reset branch + +Relocate branch to refer to ref. Branch must not contain uncommitted data. + +### Example + +* Basic Authentication (basic_auth): +* Api Key Authentication (cookie_auth): +* Api Key Authentication (oidc_auth): +* Api Key Authentication (saml_auth): +* Bearer (JWT) Authentication (jwt_token): + +```python +import time +import os +import lakefs_sdk +from lakefs_sdk.rest import ApiException +from pprint import pprint + +# Defining the host is optional and defaults to /api/v1 +# See configuration.py for a list of all supported configuration parameters. +configuration = lakefs_sdk.Configuration( + host = "/api/v1" +) + +# The client must configure the authentication and authorization parameters +# in accordance with the API server security policy. +# Examples for each auth method are provided below, use the example that +# satisfies your auth use case. + +# Configure HTTP basic authorization: basic_auth +configuration = lakefs_sdk.Configuration( + username = os.environ["USERNAME"], + password = os.environ["PASSWORD"] +) + +# Configure API key authorization: cookie_auth +configuration.api_key['cookie_auth'] = os.environ["API_KEY"] + +# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed +# configuration.api_key_prefix['cookie_auth'] = 'Bearer' + +# Configure API key authorization: oidc_auth +configuration.api_key['oidc_auth'] = os.environ["API_KEY"] + +# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed +# configuration.api_key_prefix['oidc_auth'] = 'Bearer' + +# Configure API key authorization: saml_auth +configuration.api_key['saml_auth'] = os.environ["API_KEY"] + +# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed +# configuration.api_key_prefix['saml_auth'] = 'Bearer' + +# Configure Bearer authorization (JWT): jwt_token +configuration = lakefs_sdk.Configuration( + access_token = os.environ["BEARER_TOKEN"] +) + +# Enter a context with an instance of the API client +with lakefs_sdk.ApiClient(configuration) as api_client: + # Create an instance of the API class + api_instance = lakefs_sdk.ExperimentalApi(api_client) + repository = 'repository_example' # str | + branch = 'branch_example' # str | + ref = 'ref_example' # str | After reset, branch will point at this reference. + force = False # bool | (optional) (default to False) + + try: + # hard reset branch + api_instance.hard_reset_branch(repository, branch, ref, force=force) + except Exception as e: + print("Exception when calling ExperimentalApi->hard_reset_branch: %s\n" % e) +``` + + + +### Parameters + + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **repository** | **str**| | + **branch** | **str**| | + **ref** | **str**| After reset, branch will point at this reference. | + **force** | **bool**| | [optional] [default to False] + +### Return type + +void (empty response body) + +### Authorization + +[basic_auth](../README.md#basic_auth), [cookie_auth](../README.md#cookie_auth), [oidc_auth](../README.md#oidc_auth), [saml_auth](../README.md#saml_auth), [jwt_token](../README.md#jwt_token) + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +### HTTP response details + +| Status code | Description | Response headers | +|-------------|-------------|------------------| +**204** | reset successful | - | +**400** | Bad Request | - | +**401** | Unauthorized | - | +**403** | Forbidden | - | +**404** | Resource Not Found | - | +**420** | too many requests | - | +**0** | Internal Server Error | - | + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + # **otf_diff** > OtfDiffList otf_diff(repository, left_ref, right_ref, table_path, type) diff --git a/clients/python/docs/ResetCreation.md b/clients/python/docs/ResetCreation.md index 9be627fc59e..464cacaa0dd 100644 --- a/clients/python/docs/ResetCreation.md +++ b/clients/python/docs/ResetCreation.md @@ -5,7 +5,7 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**type** | **str** | | +**type** | **str** | What to reset according to path. | **path** | **str** | | [optional] **force** | **bool** | | [optional] [default to False] diff --git a/clients/python/lakefs_sdk/api/experimental_api.py b/clients/python/lakefs_sdk/api/experimental_api.py index 8e9f71f1a58..bc5e18665bb 100644 --- a/clients/python/lakefs_sdk/api/experimental_api.py +++ b/clients/python/lakefs_sdk/api/experimental_api.py @@ -20,7 +20,7 @@ from pydantic import validate_arguments, ValidationError from typing_extensions import Annotated -from pydantic import Field, StrictInt, StrictStr +from pydantic import Field, StrictBool, StrictInt, StrictStr from typing import Optional @@ -708,6 +708,167 @@ def get_otf_diffs_with_http_info(self, **kwargs) -> ApiResponse: # noqa: E501 collection_formats=_collection_formats, _request_auth=_params.get('_request_auth')) + @validate_arguments + def hard_reset_branch(self, repository : StrictStr, branch : StrictStr, ref : Annotated[StrictStr, Field(..., description="After reset, branch will point at this reference.")], force : Optional[StrictBool] = None, **kwargs) -> None: # noqa: E501 + """hard reset branch # noqa: E501 + + Relocate branch to refer to ref. Branch must not contain uncommitted data. # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + + >>> thread = api.hard_reset_branch(repository, branch, ref, force, async_req=True) + >>> result = thread.get() + + :param repository: (required) + :type repository: str + :param branch: (required) + :type branch: str + :param ref: After reset, branch will point at this reference. (required) + :type ref: str + :param force: + :type force: bool + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: None + """ + kwargs['_return_http_data_only'] = True + if '_preload_content' in kwargs: + raise ValueError("Error! Please call the hard_reset_branch_with_http_info method with `_preload_content` instead and obtain raw data from ApiResponse.raw_data") + return self.hard_reset_branch_with_http_info(repository, branch, ref, force, **kwargs) # noqa: E501 + + @validate_arguments + def hard_reset_branch_with_http_info(self, repository : StrictStr, branch : StrictStr, ref : Annotated[StrictStr, Field(..., description="After reset, branch will point at this reference.")], force : Optional[StrictBool] = None, **kwargs) -> ApiResponse: # noqa: E501 + """hard reset branch # noqa: E501 + + Relocate branch to refer to ref. Branch must not contain uncommitted data. # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + + >>> thread = api.hard_reset_branch_with_http_info(repository, branch, ref, force, async_req=True) + >>> result = thread.get() + + :param repository: (required) + :type repository: str + :param branch: (required) + :type branch: str + :param ref: After reset, branch will point at this reference. (required) + :type ref: str + :param force: + :type force: bool + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _preload_content: if False, the ApiResponse.data will + be set to none and raw_data will store the + HTTP response body without reading/decoding. + Default is True. + :type _preload_content: bool, optional + :param _return_http_data_only: response data instead of ApiResponse + object with status code, headers, etc + :type _return_http_data_only: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the authentication + in the spec for a single request. + :type _request_auth: dict, optional + :type _content_type: string, optional: force content-type for the request + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: None + """ + + _params = locals() + + _all_params = [ + 'repository', + 'branch', + 'ref', + 'force' + ] + _all_params.extend( + [ + 'async_req', + '_return_http_data_only', + '_preload_content', + '_request_timeout', + '_request_auth', + '_content_type', + '_headers' + ] + ) + + # validate the arguments + for _key, _val in _params['kwargs'].items(): + if _key not in _all_params: + raise ApiTypeError( + "Got an unexpected keyword argument '%s'" + " to method hard_reset_branch" % _key + ) + _params[_key] = _val + del _params['kwargs'] + + _collection_formats = {} + + # process the path parameters + _path_params = {} + if _params['repository']: + _path_params['repository'] = _params['repository'] + + if _params['branch']: + _path_params['branch'] = _params['branch'] + + + # process the query parameters + _query_params = [] + if _params.get('ref') is not None: # noqa: E501 + _query_params.append(('ref', _params['ref'])) + + if _params.get('force') is not None: # noqa: E501 + _query_params.append(('force', _params['force'])) + + # process the header parameters + _header_params = dict(_params.get('_headers', {})) + # process the form parameters + _form_params = [] + _files = {} + # process the body parameter + _body_params = None + # set the HTTP header `Accept` + _header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # authentication setting + _auth_settings = ['basic_auth', 'cookie_auth', 'oidc_auth', 'saml_auth', 'jwt_token'] # noqa: E501 + + _response_types_map = {} + + return self.api_client.call_api( + '/repositories/{repository}/branches/{branch}/hard_reset', 'PUT', + _path_params, + _query_params, + _header_params, + body=_body_params, + post_params=_form_params, + files=_files, + response_types_map=_response_types_map, + auth_settings=_auth_settings, + async_req=_params.get('async_req'), + _return_http_data_only=_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=_params.get('_preload_content', True), + _request_timeout=_params.get('_request_timeout'), + collection_formats=_collection_formats, + _request_auth=_params.get('_request_auth')) + @validate_arguments def otf_diff(self, repository : StrictStr, left_ref : StrictStr, right_ref : StrictStr, table_path : Annotated[StrictStr, Field(..., description="a path to the table location under the specified ref.")], type : Annotated[StrictStr, Field(..., description="the type of otf")], **kwargs) -> OtfDiffList: # noqa: E501 """perform otf diff # noqa: E501 diff --git a/clients/python/lakefs_sdk/models/reset_creation.py b/clients/python/lakefs_sdk/models/reset_creation.py index 645f82cda94..4499aa80560 100644 --- a/clients/python/lakefs_sdk/models/reset_creation.py +++ b/clients/python/lakefs_sdk/models/reset_creation.py @@ -26,7 +26,7 @@ class ResetCreation(BaseModel): """ ResetCreation """ - type: StrictStr = Field(...) + type: StrictStr = Field(..., description="What to reset according to path.") path: Optional[StrictStr] = None force: Optional[StrictBool] = False __properties = ["type", "path", "force"] diff --git a/clients/python/test/test_experimental_api.py b/clients/python/test/test_experimental_api.py index 1ae19ae8dc5..f9d26e9fed7 100644 --- a/clients/python/test/test_experimental_api.py +++ b/clients/python/test/test_experimental_api.py @@ -57,6 +57,13 @@ def test_get_otf_diffs(self): """ pass + def test_hard_reset_branch(self): + """Test case for hard_reset_branch + + hard reset branch # noqa: E501 + """ + pass + def test_otf_diff(self): """Test case for otf_diff diff --git a/docs/assets/js/swagger.yml b/docs/assets/js/swagger.yml index 831095ebb24..27a67c22070 100644 --- a/docs/assets/js/swagger.yml +++ b/docs/assets/js/swagger.yml @@ -482,6 +482,7 @@ components: type: type: string enum: [object, common_prefix, reset] + description: What to reset according to path. path: type: string force: @@ -3379,6 +3380,55 @@ paths: default: $ref: "#/components/responses/ServerError" + /repositories/{repository}/branches/{branch}/hard_reset: + parameters: + - in: path + name: repository + required: true + schema: + type: string + - in: path + name: branch + required: true + schema: + type: string + put: + tags: + - experimental + operationId: hardResetBranch + summary: hard reset branch + description: + Relocate branch to refer to ref. Branch must not contain + uncommitted data. + parameters: + - in: query + name: ref + required: true + schema: + type: string + description: After reset, branch will point at this reference. + - in: query + name: force + required: false + schema: + type: boolean + default: false + responses: + 204: + description: reset successful + 400: + $ref: "#/components/responses/BadRequest" + 401: + $ref: "#/components/responses/Unauthorized" + 403: + $ref: "#/components/responses/Forbidden" + 404: + $ref: "#/components/responses/NotFound" + 420: + description: too many requests + default: + $ref: "#/components/responses/ServerError" + /repositories/{repository}/branches/{branch}/revert: parameters: - in: path diff --git a/esti/rollback_test.go b/esti/rollback_test.go index 074fb483080..4a5efeaa32d 100644 --- a/esti/rollback_test.go +++ b/esti/rollback_test.go @@ -4,6 +4,7 @@ import ( "fmt" "testing" + "github.com/go-openapi/swag" "github.com/stretchr/testify/require" "github.com/treeverse/lakefs/pkg/api/apigen" ) @@ -11,7 +12,8 @@ import ( func TestResetAll(t *testing.T) { ctx, _, repo := setupTest(t) defer tearDownTest(repo) - objPath := "1.txt" + + const objPath = "1.txt" // upload file _, objContent := uploadFileRandomData(ctx, t, repo, mainBranch, objPath) @@ -36,9 +38,8 @@ func TestResetAll(t *testing.T) { "failed to delete file %s repo %s branch %s", objPath, repo, mainBranch) // reset - reset := apigen.ResetCreation{ - Type: "reset", - } + reset := apigen.ResetCreation{Type: "reset"} + resetResp, err := client.ResetBranchWithResponse(ctx, repo, mainBranch, apigen.ResetBranchJSONRequestBody(reset)) require.NoError(t, err, "failed to reset") require.NoErrorf(t, verifyResponse(resetResp.HTTPResponse, resetResp.Body), @@ -55,6 +56,49 @@ func TestResetAll(t *testing.T) { require.Equal(t, objContent, body, fmt.Sprintf("path: %s, expected: %s, actual:%s", objPath, objContent, body)) } +func TestHardReset(t *testing.T) { + ctx, _, repo := setupTest(t) + defer tearDownTest(repo) + + const objPath = "1.txt" + + // upload file + _, _ = uploadFileRandomData(ctx, t, repo, mainBranch, objPath) + f, err := found(ctx, repo, mainBranch, objPath) + require.NoError(t, err) + require.True(t, f, "uploaded object found") + + // commit file + commitResp, err := client.CommitWithResponse(ctx, repo, mainBranch, &apigen.CommitParams{}, apigen.CommitJSONRequestBody{ + Message: "resetAll", + }) + require.NoError(t, err, "failed to commit changes") + require.NoErrorf(t, verifyResponse(commitResp.HTTPResponse, commitResp.Body), + "failed to commit changes repo %s branch %s", repo, mainBranch) + + // commit again so we have something to revert + _, err = client.CommitWithResponse(ctx, repo, mainBranch, &apigen.CommitParams{}, apigen.CommitJSONRequestBody{ + Message: "another commit", + AllowEmpty: swag.Bool(true), + }) + + // reset + reset := apigen.HardResetBranchParams{Ref: mainBranch + "~"} + + resetResp, err := client.HardResetBranchWithResponse(ctx, repo, mainBranch, &reset) + require.NoError(t, err, "failed to reset") + require.NoErrorf(t, verifyResponse(resetResp.HTTPResponse, resetResp.Body), + "failed to reset commit %s repo %s branch %s", repo, mainBranch) + + // examine the last commit + getCommitResp, err := client.GetCommitWithResponse(ctx, repo, mainBranch) + require.NoError(t, err, "failed to get latest commit") + require.NoErrorf(t, verifyResponse(getCommitResp.HTTPResponse, getCommitResp.Body), + "failed to get commit repo %s ref %s", repo, mainBranch) + require.Equal(t, *commitResp.JSON201, *getCommitResp.JSON200, + "Hard-reset should yield exact commit") +} + func TestResetPath(t *testing.T) { ctx, _, repo := setupTest(t) defer tearDownTest(repo) diff --git a/pkg/api/controller.go b/pkg/api/controller.go index 1dfb7f3d6c6..2a5a5f80dd5 100644 --- a/pkg/api/controller.go +++ b/pkg/api/controller.go @@ -2595,6 +2595,7 @@ func (c *Controller) ResetBranch(w http.ResponseWriter, r *http.Request, body ap c.LogAction(ctx, "reset_branch", r, repository, branch, "") var err error + switch body.Type { case entryTypeCommonPrefix: err = c.Catalog.ResetEntries(ctx, repository, branch, swag.StringValue(body.Path), graveler.WithForce(swag.BoolValue(body.Force))) @@ -2606,6 +2607,28 @@ func (c *Controller) ResetBranch(w http.ResponseWriter, r *http.Request, body ap writeError(w, r, http.StatusBadRequest, "unknown reset type") return } + + if c.handleAPIError(ctx, w, r, err) { + return + } + writeResponse(w, r, http.StatusNoContent, nil) +} + +func (c *Controller) HardResetBranch(w http.ResponseWriter, r *http.Request, repository, branch string, params apigen.HardResetBranchParams) { + if !c.authorize(w, r, permissions.Node{ + Permission: permissions.Permission{ + // TODO(ozkatz): Can we have another action here? + Action: permissions.RevertBranchAction, + Resource: permissions.BranchArn(repository, branch), + }, + }) { + return + } + ctx := r.Context() + + c.LogAction(ctx, "hard_reset_branch", r, repository, branch, "") + + err := c.Catalog.HardResetBranch(ctx, repository, branch, params.Ref, graveler.WithForce(swag.BoolValue(params.Force))) if c.handleAPIError(ctx, w, r, err) { return } diff --git a/pkg/catalog/catalog.go b/pkg/catalog/catalog.go index d6798fa3ae1..fb3e60c5f84 100644 --- a/pkg/catalog/catalog.go +++ b/pkg/catalog/catalog.go @@ -748,6 +748,23 @@ func (c *Catalog) GetBranchReference(ctx context.Context, repositoryID string, b return string(b.CommitID), nil } +func (c *Catalog) HardResetBranch(ctx context.Context, repositoryID, branch, refExpr string, opts ...graveler.SetOptionsFunc) error { + branchID := graveler.BranchID(branch) + ref := graveler.Ref(refExpr) + if err := validator.Validate([]validator.ValidateArg{ + {Name: "repository", Value: repositoryID, Fn: graveler.ValidateRepositoryID}, + {Name: "branch", Value: branchID, Fn: graveler.ValidateBranchID}, + {Name: "ref", Value: ref, Fn: graveler.ValidateRef}, + }); err != nil { + return err + } + repository, err := c.getRepository(ctx, repositoryID) + if err != nil { + return err + } + return c.Store.ResetHard(ctx, repository, branchID, ref, opts...) +} + func (c *Catalog) ResetBranch(ctx context.Context, repositoryID string, branch string, opts ...graveler.SetOptionsFunc) error { branchID := graveler.BranchID(branch) if err := validator.Validate([]validator.ValidateArg{ @@ -1154,6 +1171,7 @@ func (c *Catalog) Commit(ctx context.Context, repositoryID, branch, message, com catalogCommitLog.Parents = append(catalogCommitLog.Parents, parent.String()) } catalogCommitLog.CreationDate = commit.CreationDate.UTC() + catalogCommitLog.MetaRangeID = string(commit.MetaRangeID) return catalogCommitLog, nil } diff --git a/pkg/graveler/graveler.go b/pkg/graveler/graveler.go index e571ffd5946..01d9a6bcf1e 100644 --- a/pkg/graveler/graveler.go +++ b/pkg/graveler/graveler.go @@ -561,6 +561,9 @@ type VersionController interface { // ResolveRawRef returns the ResolvedRef matching the given RawRef ResolveRawRef(ctx context.Context, repository *RepositoryRecord, rawRef RawRef) (*ResolvedRef, error) + // ResetHard resets branch to point at ref. + ResetHard(ctx context.Context, repository *RepositoryRecord, branchID BranchID, ref Ref, opts ...SetOptionsFunc) error + // Reset throws all staged data on the repository / branch Reset(ctx context.Context, repository *RepositoryRecord, branchID BranchID, opts ...SetOptionsFunc) error @@ -2226,6 +2229,70 @@ func (g *Graveler) dropTokens(ctx context.Context, tokens ...StagingToken) { } } +func (g *Graveler) ResetHard(ctx context.Context, repository *RepositoryRecord, branchID BranchID, ref Ref, opts ...SetOptionsFunc) error { + isProtectedCommit, err := g.protectedBranchesManager.IsBlocked(ctx, repository, branchID, BranchProtectionBlockedAction_COMMIT) + if err != nil { + return err + } + if isProtectedCommit { + return ErrProtectedBranch + } + isProtectedWrite, err := g.protectedBranchesManager.IsBlocked(ctx, repository, branchID, BranchProtectionBlockedAction_STAGING_WRITE) + if err != nil { + return err + } + if isProtectedWrite { + return ErrWriteToProtectedBranch + } + + options := &SetOptions{} + for _, opt := range opts { + opt(options) + } + + if repository.ReadOnly && !options.Force { + return ErrReadOnlyRepository + } + + // TODO(ariels): up to here. Verify staging is empty! + err = g.retryBranchUpdate(ctx, repository, branchID, func(branch *Branch) (*Branch, error) { + if empty, err := g.isSealedEmpty(ctx, repository, branch); err != nil { + return nil, fmt.Errorf("%s: check if dirty: %w", branchID, err) + } else if !empty { + return nil, fmt.Errorf("%s: %w", branchID, ErrDirtyBranch) + } + // Must fetch ref under update! Consider a hard reset to + // branch^ racing against a commit D to branch. Say branch + // looks like this: + // + // C --> B --> A + // + // There are 2 possible results: + // + // * If commit occurs first then D --> C --> B --> A and + // then the hard reset goes to C --> B --> A. + // + // * If the hard reset goes first then B --> A and then the + // commit goes to D --> B --> A. + // + // But a third option is possible if dereference occurs + // outside of BranchUpdate. Hard reset dereferences branch^ + // to B, then commit goes to D --> C --> B --> A, then hard + // reset enters BranchUpdate and we end up with B --> A. + // That result is not serialized: it cannot be explained + // through atomic operations. + // + // So we need to dereference here :-( + commitRecord, err := g.dereferenceCommit(ctx, repository, ref) + if err != nil { + return nil, fmt.Errorf("hard-reset %s to %s: %w", branchID, ref, err) + } + branch.CommitID = commitRecord.CommitID + return branch, nil + }, "reset_hard") + return err +} + func (g *Graveler) Reset(ctx context.Context, repository *RepositoryRecord, branchID BranchID, opts ...SetOptionsFunc) error { isProtected, err := g.protectedBranchesManager.IsBlocked(ctx, repository, branchID, BranchProtectionBlockedAction_STAGING_WRITE) if err != nil { diff --git a/pkg/graveler/mock/graveler.go b/pkg/graveler/mock/graveler.go index d5834a9edf3..5f401e8877d 100644 --- a/pkg/graveler/mock/graveler.go +++ b/pkg/graveler/mock/graveler.go @@ -801,6 +801,25 @@ func (mr *MockVersionControllerMockRecorder) Reset(ctx, repository, branchID int return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Reset", reflect.TypeOf((*MockVersionController)(nil).Reset), varargs...) } +// ResetHard mocks base method. +func (m *MockVersionController) ResetHard(ctx context.Context, repository *graveler.RepositoryRecord, branchID graveler.BranchID, ref graveler.Ref, opts ...graveler.SetOptionsFunc) error { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, repository, branchID, ref} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "ResetHard", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// ResetHard indicates an expected call of ResetHard. +func (mr *MockVersionControllerMockRecorder) ResetHard(ctx, repository, branchID, ref interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, repository, branchID, ref}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResetHard", reflect.TypeOf((*MockVersionController)(nil).ResetHard), varargs...) +} + // ResetKey mocks base method. func (m *MockVersionController) ResetKey(ctx context.Context, repository *graveler.RepositoryRecord, branchID graveler.BranchID, key graveler.Key, opts ...graveler.SetOptionsFunc) error { m.ctrl.T.Helper()