From 510b7a5efab9706e42ecebbf5a69ddbb9439b742 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Saugat=20Pachhai=20=28=E0=A4=B8=E0=A5=8C=E0=A4=97=E0=A4=BE?= =?UTF-8?q?=E0=A4=A4=29?= Date: Wed, 23 Jul 2025 14:08:14 +0545 Subject: [PATCH 1/2] initial changes --- api/swagger.yml | 38 +++++++++++++ pkg/actions/errors.go | 1 + pkg/actions/service.go | 66 ++++++++++++++++++++++- pkg/api/controller.go | 32 ++++++++++- pkg/graveler/hooks_handler.go | 3 ++ pkg/graveler/hooks_handler_isvalid.gen.go | 3 +- pkg/permissions/actions.gen.go | 1 + pkg/permissions/actions.go | 1 + 8 files changed, 142 insertions(+), 3 deletions(-) diff --git a/api/swagger.yml b/api/swagger.yml index f4b3407a199..06b648570a7 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -5750,6 +5750,44 @@ paths: default: $ref: "#/components/responses/ServerError" + /repositories/{repository}/refs/{ref}/actions/{action}/triggers: + post: + tags: + - actions + operationId: triggerAction + summary: manually trigger an action + parameters: + - in: path + name: repository + required: true + schema: + type: string + - in: path + name: ref + required: true + schema: + type: string + - in: path + name: action + required: true + schema: + type: string + responses: + 202: + description: action accepted for background execution + 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}/metadata/object/{type}/{object_id}: parameters: - in: path diff --git a/pkg/actions/errors.go b/pkg/actions/errors.go index 065e793eda9..7da4e9cdb2b 100644 --- a/pkg/actions/errors.go +++ b/pkg/actions/errors.go @@ -11,4 +11,5 @@ var ( ErrUnknownHookType = errors.New("unknown hook type") ErrInvalidAction = errors.New("invalid action") ErrInvalidEventParameter = errors.New("invalid event parameter") + ErrActionsDisabled = errors.New("actions are disabled") ) diff --git a/pkg/actions/service.go b/pkg/actions/service.go index cb5e70986a9..9e820ac9068 100644 --- a/pkg/actions/service.go +++ b/pkg/actions/service.go @@ -16,6 +16,7 @@ import ( "github.com/hashicorp/go-multierror" "github.com/treeverse/lakefs/pkg/actions/lua/hook" "github.com/treeverse/lakefs/pkg/auth" + "github.com/treeverse/lakefs/pkg/catalog" "github.com/treeverse/lakefs/pkg/graveler" "github.com/treeverse/lakefs/pkg/kv" "github.com/treeverse/lakefs/pkg/logging" @@ -210,6 +211,7 @@ type Service interface { GetTaskResult(ctx context.Context, repositoryID string, runID string, hookRunID string) (*TaskResult, error) ListRunResults(ctx context.Context, repositoryID string, branchID, commitID string, after string) (RunResultIterator, error) ListRunTaskResults(ctx context.Context, repositoryID string, runID string, after string) (TaskResultIterator, error) + TriggerAction(ctx context.Context, repository *catalog.Repository, ref string, actionName string) (string, error) graveler.HooksHandler } @@ -303,7 +305,23 @@ func (s *StoreService) loadMatchedActions(ctx context.Context, record graveler.H if err != nil { return nil, err } - return MatchedActions(actions, spec) + + // First filter by event type and branch + matchedActions, err := MatchedActions(actions, spec) + if err != nil { + return nil, err + } + + // If ActionName is specified in the record, filter by it + if record.ActionName != "" { + for _, action := range matchedActions { + if action.Name == record.ActionName { + return []*Action{action}, nil + } + } + return nil, ErrNotFound + } + return matchedActions, nil } func (s *StoreService) allocateTasks(runID string, actions []*Action) ([][]*Task, error) { @@ -606,6 +624,52 @@ func (s *StoreService) PostCherryPickHook(ctx context.Context, record graveler.H return nil } +func (s *StoreService) TriggerAction(ctx context.Context, repository *catalog.Repository, ref string, actionName string) (string, error) { + if !s.cfg.Enabled { + logging.FromContext(ctx).WithField("repository", repository.Name).Debug("Hooks are disabled, skipping manual trigger") + return "", ErrActionsDisabled + } + + // Construct graveler.RepositoryRecord from catalog.Repository + repoRecord := &graveler.RepositoryRecord{ + RepositoryID: graveler.RepositoryID(repository.Name), + Repository: &graveler.Repository{ + StorageID: graveler.StorageID(repository.StorageID), + StorageNamespace: graveler.StorageNamespace(repository.StorageNamespace), + CreationDate: repository.CreationDate, + DefaultBranchID: graveler.BranchID(repository.DefaultBranch), + State: graveler.RepositoryState_ACTIVE, + InstanceUID: "", // Not available from catalog API + ReadOnly: repository.ReadOnly, + }, + } + + runID := s.NewRunID() + + // Create hook record for manual trigger with all required fields + record := graveler.HookRecord{ + RunID: runID, + EventType: graveler.EventTypeManualTrigger, + Repository: repoRecord, + SourceRef: graveler.Ref(ref), + BranchID: graveler.BranchID(ref), // For manual triggers, we use the ref as branch + ActionName: actionName, + } + + // verify that the action exists before scheduling the run + spec := MatchSpec{ + EventType: record.EventType, + BranchID: record.BranchID, + } + _, err := s.loadMatchedActions(ctx, record, spec) + if err != nil { + return "", ErrNotFound + } + // Run the action asynchronously + s.asyncRun(ctx, record) + return runID, nil +} + func (s *StoreService) NewRunID() string { return s.idGen.NewRunID() } diff --git a/pkg/api/controller.go b/pkg/api/controller.go index c38806269b4..4dc1610c76d 100644 --- a/pkg/api/controller.go +++ b/pkg/api/controller.go @@ -87,6 +87,7 @@ type actionsHandler interface { GetTaskResult(ctx context.Context, repositoryID, runID, hookRunID string) (*actions.TaskResult, error) ListRunResults(ctx context.Context, repositoryID, branchID, commitID, after string) (actions.RunResultIterator, error) ListRunTaskResults(ctx context.Context, repositoryID, runID, after string) (actions.TaskResultIterator, error) + TriggerAction(ctx context.Context, repository *catalog.Repository, ref, actionName string) (string, error) } type Migrator interface { @@ -2737,6 +2738,34 @@ func (c *Controller) GetRun(w http.ResponseWriter, r *http.Request, repository, writeResponse(w, r, http.StatusOK, response) } +func (c *Controller) TriggerAction(w http.ResponseWriter, r *http.Request, repository, refParam, action string) { + if !c.authorize(w, r, permissions.Node{ + Permission: permissions.Permission{ + Action: permissions.TriggerActionsAction, + Resource: permissions.RepoArn(repository), + }, + }) { + return + } + ctx := r.Context() + c.LogAction(ctx, "actions_trigger", r, repository, refParam, "") + + // Get repository information + repo, err := c.Catalog.GetRepository(ctx, repository) + if c.handleAPIError(ctx, w, r, err) { + return + } + + // Trigger the action + _, err = c.Actions.TriggerAction(ctx, repo, refParam, action) + if c.handleAPIError(ctx, w, r, err) { + return + } + + // For async execution, just return 202 Accepted + writeResponse(w, r, http.StatusAccepted, nil) +} + func (c *Controller) ListRunHooks(w http.ResponseWriter, r *http.Request, repository, runID string, params apigen.ListRunHooksParams) { if !c.authorize(w, r, permissions.Node{ Permission: permissions.Permission{ @@ -2986,7 +3015,8 @@ func (c *Controller) handleAPIErrorCallback(ctx context.Context, w http.Response case errors.Is(err, block.ErrForbidden), errors.Is(err, graveler.ErrProtectedBranch), errors.Is(err, graveler.ErrReadOnlyRepository), - errors.Is(err, graveler.ErrDeleteDefaultBranch): + errors.Is(err, graveler.ErrDeleteDefaultBranch), + errors.Is(err, actions.ErrActionsDisabled): cb(w, r, http.StatusForbidden, err) case errors.Is(err, authentication.ErrSessionExpired): diff --git a/pkg/graveler/hooks_handler.go b/pkg/graveler/hooks_handler.go index 5c0759fac56..89144faf773 100644 --- a/pkg/graveler/hooks_handler.go +++ b/pkg/graveler/hooks_handler.go @@ -30,6 +30,7 @@ const ( EventTypePostRevert EventType = "post-revert" EventTypePreCherryPick EventType = "pre-cherry-pick" EventTypePostCherryPick EventType = "post-cherry-pick" + EventTypeManualTrigger EventType = "manual-trigger" ) // UnixYear3000 used by NewRunID for generating run IDs in reverse order @@ -56,6 +57,8 @@ type HookRecord struct { TagID TagID // Exists only in merge actions. Contains the requested source to merge from (branch/tag/ref) as requested in the merge request MergeSource Ref + // Exists only in manual trigger actions. Contains the action name that was triggered + ActionName string } type HooksHandler interface { diff --git a/pkg/graveler/hooks_handler_isvalid.gen.go b/pkg/graveler/hooks_handler_isvalid.gen.go index d69046031f7..f98f1c3d6b3 100644 --- a/pkg/graveler/hooks_handler_isvalid.gen.go +++ b/pkg/graveler/hooks_handler_isvalid.gen.go @@ -20,7 +20,8 @@ func (v EventType) IsValid() bool { EventTypePreRevert, EventTypePostRevert, EventTypePreCherryPick, - EventTypePostCherryPick: + EventTypePostCherryPick, + EventTypeManualTrigger: return true default: return false diff --git a/pkg/permissions/actions.gen.go b/pkg/permissions/actions.gen.go index b61970136ed..89d7515b8d6 100644 --- a/pkg/permissions/actions.gen.go +++ b/pkg/permissions/actions.gen.go @@ -52,6 +52,7 @@ var Actions = []string{ "auth:DeleteUserExternalPrincipal", "auth:ReadExternalPrincipal", "ci:ReadAction", + "ci:TriggerAction", "retention:PrepareGarbageCollectionCommits", "retention:GetGarbageCollectionRules", "retention:SetGarbageCollectionRules", diff --git a/pkg/permissions/actions.go b/pkg/permissions/actions.go index 99c5418d4ab..85ba0536a4e 100644 --- a/pkg/permissions/actions.go +++ b/pkg/permissions/actions.go @@ -65,6 +65,7 @@ const ( DeleteUserExternalPrincipalAction = "auth:DeleteUserExternalPrincipal" ReadExternalPrincipalAction = "auth:ReadExternalPrincipal" ReadActionsAction = "ci:ReadAction" + TriggerActionsAction = "ci:TriggerAction" PrepareGarbageCollectionCommitsAction = "retention:PrepareGarbageCollectionCommits" GetGarbageCollectionRulesAction = "retention:GetGarbageCollectionRules" SetGarbageCollectionRulesAction = "retention:SetGarbageCollectionRules" From 09e05afe22c4ced35c46fb33298d9a1f15ef2a83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Saugat=20Pachhai=20=28=E0=A4=B8=E0=A5=8C=E0=A4=97=E0=A4=BE?= =?UTF-8?q?=E0=A4=A4=29?= Date: Wed, 23 Jul 2025 22:25:50 +0545 Subject: [PATCH 2/2] autogenerated changes --- clients/java/README.md | 1 + clients/java/api/openapi.yaml | 64 ++++++ clients/java/docs/ActionsApi.md | 99 +++++++++ .../io/lakefs/clients/sdk/ActionsApi.java | 196 ++++++++++++++++++ .../io/lakefs/clients/sdk/ActionsApiTest.java | 15 ++ clients/python/README.md | 1 + clients/python/docs/ActionsApi.md | 114 ++++++++++ clients/python/lakefs_sdk/api/actions_api.py | 152 ++++++++++++++ clients/python/test/test_actions_api.py | 7 + clients/rust/README.md | 1 + clients/rust/docs/ActionsApi.md | 31 +++ clients/rust/src/apis/actions_api.rs | 46 ++++ 12 files changed, 727 insertions(+) diff --git a/clients/java/README.md b/clients/java/README.md index f2e2f23010e..9bd6b65cdc6 100644 --- a/clients/java/README.md +++ b/clients/java/README.md @@ -147,6 +147,7 @@ Class | Method | HTTP request | Description *ActionsApi* | [**getRunHookOutput**](docs/ActionsApi.md#getRunHookOutput) | **GET** /repositories/{repository}/actions/runs/{run_id}/hooks/{hook_run_id}/output | get run hook output *ActionsApi* | [**listRepositoryRuns**](docs/ActionsApi.md#listRepositoryRuns) | **GET** /repositories/{repository}/actions/runs | list runs *ActionsApi* | [**listRunHooks**](docs/ActionsApi.md#listRunHooks) | **GET** /repositories/{repository}/actions/runs/{run_id}/hooks | list run hooks +*ActionsApi* | [**triggerAction**](docs/ActionsApi.md#triggerAction) | **POST** /repositories/{repository}/refs/{ref}/actions/{action}/triggers | manually trigger an action *AuthApi* | [**addGroupMembership**](docs/AuthApi.md#addGroupMembership) | **PUT** /auth/groups/{groupId}/members/{userId} | add group membership *AuthApi* | [**attachPolicyToGroup**](docs/AuthApi.md#attachPolicyToGroup) | **PUT** /auth/groups/{groupId}/policies/{policyId} | attach policy to group *AuthApi* | [**attachPolicyToUser**](docs/AuthApi.md#attachPolicyToUser) | **PUT** /auth/users/{userId}/policies/{policyId} | attach policy to user diff --git a/clients/java/api/openapi.yaml b/clients/java/api/openapi.yaml index aedc95cc58c..7604e689e3c 100644 --- a/clients/java/api/openapi.yaml +++ b/clients/java/api/openapi.yaml @@ -6468,6 +6468,70 @@ paths: tags: - actions x-accepts: application/json + /repositories/{repository}/refs/{ref}/actions/{action}/triggers: + post: + operationId: triggerAction + parameters: + - explode: false + in: path + name: repository + required: true + schema: + type: string + style: simple + - explode: false + in: path + name: ref + required: true + schema: + type: string + style: simple + - explode: false + in: path + name: action + required: true + schema: + type: string + style: simple + responses: + "202": + description: action accepted for background execution + "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: manually trigger an action + tags: + - actions + x-accepts: application/json /repositories/{repository}/metadata/object/{type}/{object_id}: get: operationId: getMetadataObject diff --git a/clients/java/docs/ActionsApi.md b/clients/java/docs/ActionsApi.md index 2a07e4b016d..338e0ce3405 100644 --- a/clients/java/docs/ActionsApi.md +++ b/clients/java/docs/ActionsApi.md @@ -8,6 +8,7 @@ All URIs are relative to */api/v1* | [**getRunHookOutput**](ActionsApi.md#getRunHookOutput) | **GET** /repositories/{repository}/actions/runs/{run_id}/hooks/{hook_run_id}/output | get run hook output | | [**listRepositoryRuns**](ActionsApi.md#listRepositoryRuns) | **GET** /repositories/{repository}/actions/runs | list runs | | [**listRunHooks**](ActionsApi.md#listRunHooks) | **GET** /repositories/{repository}/actions/runs/{run_id}/hooks | list run hooks | +| [**triggerAction**](ActionsApi.md#triggerAction) | **POST** /repositories/{repository}/refs/{ref}/actions/{action}/triggers | manually trigger an action | @@ -408,3 +409,101 @@ public class Example { | **429** | too many requests | - | | **0** | Internal Server Error | - | + +# **triggerAction** +> triggerAction(repository, ref, action).execute(); + +manually trigger an action + +### 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.ActionsApi; + +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"); + + ActionsApi apiInstance = new ActionsApi(defaultClient); + String repository = "repository_example"; // String | + String ref = "ref_example"; // String | + String action = "action_example"; // String | + try { + apiInstance.triggerAction(repository, ref, action) + .execute(); + } catch (ApiException e) { + System.err.println("Exception when calling ActionsApi#triggerAction"); + 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**| | | +| **ref** | **String**| | | +| **action** | **String**| | | + +### 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 | +|-------------|-------------|------------------| +| **202** | action accepted for background execution | - | +| **400** | Bad Request | - | +| **401** | Unauthorized | - | +| **403** | Forbidden | - | +| **404** | Resource Not Found | - | +| **420** | too many requests | - | +| **0** | Internal Server Error | - | + diff --git a/clients/java/src/main/java/io/lakefs/clients/sdk/ActionsApi.java b/clients/java/src/main/java/io/lakefs/clients/sdk/ActionsApi.java index c050dabe1fa..101007d7eae 100644 --- a/clients/java/src/main/java/io/lakefs/clients/sdk/ActionsApi.java +++ b/clients/java/src/main/java/io/lakefs/clients/sdk/ActionsApi.java @@ -891,4 +891,200 @@ public okhttp3.Call executeAsync(final ApiCallback _callback) throw public APIlistRunHooksRequest listRunHooks(String repository, String runId) { return new APIlistRunHooksRequest(repository, runId); } + private okhttp3.Call triggerActionCall(String repository, String ref, String action, 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}/refs/{ref}/actions/{action}/triggers" + .replace("{" + "repository" + "}", localVarApiClient.escapeString(repository.toString())) + .replace("{" + "ref" + "}", localVarApiClient.escapeString(ref.toString())) + .replace("{" + "action" + "}", localVarApiClient.escapeString(action.toString())); + + List localVarQueryParams = new ArrayList(); + List localVarCollectionQueryParams = new ArrayList(); + Map localVarHeaderParams = new HashMap(); + Map localVarCookieParams = new HashMap(); + Map localVarFormParams = new HashMap(); + + 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, "POST", localVarQueryParams, localVarCollectionQueryParams, localVarPostBody, localVarHeaderParams, localVarCookieParams, localVarFormParams, localVarAuthNames, _callback); + } + + @SuppressWarnings("rawtypes") + private okhttp3.Call triggerActionValidateBeforeCall(String repository, String ref, String action, 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 triggerAction(Async)"); + } + + // verify the required parameter 'ref' is set + if (ref == null) { + throw new ApiException("Missing the required parameter 'ref' when calling triggerAction(Async)"); + } + + // verify the required parameter 'action' is set + if (action == null) { + throw new ApiException("Missing the required parameter 'action' when calling triggerAction(Async)"); + } + + return triggerActionCall(repository, ref, action, _callback); + + } + + + private ApiResponse triggerActionWithHttpInfo(String repository, String ref, String action) throws ApiException { + okhttp3.Call localVarCall = triggerActionValidateBeforeCall(repository, ref, action, null); + return localVarApiClient.execute(localVarCall); + } + + private okhttp3.Call triggerActionAsync(String repository, String ref, String action, final ApiCallback _callback) throws ApiException { + + okhttp3.Call localVarCall = triggerActionValidateBeforeCall(repository, ref, action, _callback); + localVarApiClient.executeAsync(localVarCall, _callback); + return localVarCall; + } + + public class APItriggerActionRequest { + private final String repository; + private final String ref; + private final String action; + + private APItriggerActionRequest(String repository, String ref, String action) { + this.repository = repository; + this.ref = ref; + this.action = action; + } + + /** + * Build call for triggerAction + * @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
202 action accepted for background execution -
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 triggerActionCall(repository, ref, action, _callback); + } + + /** + * Execute triggerAction 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
202 action accepted for background execution -
400 Bad Request -
401 Unauthorized -
403 Forbidden -
404 Resource Not Found -
420 too many requests -
0 Internal Server Error -
+ */ + public void execute() throws ApiException { + triggerActionWithHttpInfo(repository, ref, action); + } + + /** + * Execute triggerAction 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
202 action accepted for background execution -
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 triggerActionWithHttpInfo(repository, ref, action); + } + + /** + * Execute triggerAction 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
202 action accepted for background execution -
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 triggerActionAsync(repository, ref, action, _callback); + } + } + + /** + * manually trigger an action + * + * @param repository (required) + * @param ref (required) + * @param action (required) + * @return APItriggerActionRequest + * @http.response.details + + + + + + + + + +
Status Code Description Response Headers
202 action accepted for background execution -
400 Bad Request -
401 Unauthorized -
403 Forbidden -
404 Resource Not Found -
420 too many requests -
0 Internal Server Error -
+ */ + public APItriggerActionRequest triggerAction(String repository, String ref, String action) { + return new APItriggerActionRequest(repository, ref, action); + } } diff --git a/clients/java/src/test/java/io/lakefs/clients/sdk/ActionsApiTest.java b/clients/java/src/test/java/io/lakefs/clients/sdk/ActionsApiTest.java index b95d450313a..9e817f7344c 100644 --- a/clients/java/src/test/java/io/lakefs/clients/sdk/ActionsApiTest.java +++ b/clients/java/src/test/java/io/lakefs/clients/sdk/ActionsApiTest.java @@ -103,4 +103,19 @@ public void listRunHooksTest() throws ApiException { // TODO: test validations } + /** + * manually trigger an action + * + * @throws ApiException if the Api call fails + */ + @Test + public void triggerActionTest() throws ApiException { + String repository = null; + String ref = null; + String action = null; + api.triggerAction(repository, ref, action) + .execute(); + // TODO: test validations + } + } diff --git a/clients/python/README.md b/clients/python/README.md index 8ea4322e521..8c46ab5db22 100644 --- a/clients/python/README.md +++ b/clients/python/README.md @@ -123,6 +123,7 @@ Class | Method | HTTP request | Description *ActionsApi* | [**get_run_hook_output**](docs/ActionsApi.md#get_run_hook_output) | **GET** /repositories/{repository}/actions/runs/{run_id}/hooks/{hook_run_id}/output | get run hook output *ActionsApi* | [**list_repository_runs**](docs/ActionsApi.md#list_repository_runs) | **GET** /repositories/{repository}/actions/runs | list runs *ActionsApi* | [**list_run_hooks**](docs/ActionsApi.md#list_run_hooks) | **GET** /repositories/{repository}/actions/runs/{run_id}/hooks | list run hooks +*ActionsApi* | [**trigger_action**](docs/ActionsApi.md#trigger_action) | **POST** /repositories/{repository}/refs/{ref}/actions/{action}/triggers | manually trigger an action *AuthApi* | [**add_group_membership**](docs/AuthApi.md#add_group_membership) | **PUT** /auth/groups/{groupId}/members/{userId} | add group membership *AuthApi* | [**attach_policy_to_group**](docs/AuthApi.md#attach_policy_to_group) | **PUT** /auth/groups/{groupId}/policies/{policyId} | attach policy to group *AuthApi* | [**attach_policy_to_user**](docs/AuthApi.md#attach_policy_to_user) | **PUT** /auth/users/{userId}/policies/{policyId} | attach policy to user diff --git a/clients/python/docs/ActionsApi.md b/clients/python/docs/ActionsApi.md index a4b78ac3ff2..f9a14ca6297 100644 --- a/clients/python/docs/ActionsApi.md +++ b/clients/python/docs/ActionsApi.md @@ -8,6 +8,7 @@ Method | HTTP request | Description [**get_run_hook_output**](ActionsApi.md#get_run_hook_output) | **GET** /repositories/{repository}/actions/runs/{run_id}/hooks/{hook_run_id}/output | get run hook output [**list_repository_runs**](ActionsApi.md#list_repository_runs) | **GET** /repositories/{repository}/actions/runs | list runs [**list_run_hooks**](ActionsApi.md#list_run_hooks) | **GET** /repositories/{repository}/actions/runs/{run_id}/hooks | list run hooks +[**trigger_action**](ActionsApi.md#trigger_action) | **POST** /repositories/{repository}/refs/{ref}/actions/{action}/triggers | manually trigger an action # **get_run** @@ -469,3 +470,116 @@ Name | Type | Description | Notes [[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) +# **trigger_action** +> trigger_action(repository, ref, action) + +manually trigger an action + +### 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.ActionsApi(api_client) + repository = 'repository_example' # str | + ref = 'ref_example' # str | + action = 'action_example' # str | + + try: + # manually trigger an action + api_instance.trigger_action(repository, ref, action) + except Exception as e: + print("Exception when calling ActionsApi->trigger_action: %s\n" % e) +``` + + + +### Parameters + + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **repository** | **str**| | + **ref** | **str**| | + **action** | **str**| | + +### 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 | +|-------------|-------------|------------------| +**202** | action accepted for background execution | - | +**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) + diff --git a/clients/python/lakefs_sdk/api/actions_api.py b/clients/python/lakefs_sdk/api/actions_api.py index 6f03d61c930..cae3a68cfae 100644 --- a/clients/python/lakefs_sdk/api/actions_api.py +++ b/clients/python/lakefs_sdk/api/actions_api.py @@ -697,3 +697,155 @@ def list_run_hooks_with_http_info(self, repository : StrictStr, run_id : StrictS _request_timeout=_params.get('_request_timeout'), collection_formats=_collection_formats, _request_auth=_params.get('_request_auth')) + + @validate_arguments + def trigger_action(self, repository : StrictStr, ref : StrictStr, action : StrictStr, **kwargs) -> None: # noqa: E501 + """manually trigger an action # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + + >>> thread = api.trigger_action(repository, ref, action, async_req=True) + >>> result = thread.get() + + :param repository: (required) + :type repository: str + :param ref: (required) + :type ref: str + :param action: (required) + :type action: str + :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: + message = "Error! Please call the trigger_action_with_http_info method with `_preload_content` instead and obtain raw data from ApiResponse.raw_data" # noqa: E501 + raise ValueError(message) + return self.trigger_action_with_http_info(repository, ref, action, **kwargs) # noqa: E501 + + @validate_arguments + def trigger_action_with_http_info(self, repository : StrictStr, ref : StrictStr, action : StrictStr, **kwargs) -> ApiResponse: # noqa: E501 + """manually trigger an action # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + + >>> thread = api.trigger_action_with_http_info(repository, ref, action, async_req=True) + >>> result = thread.get() + + :param repository: (required) + :type repository: str + :param ref: (required) + :type ref: str + :param action: (required) + :type action: str + :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', + 'ref', + 'action' + ] + _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 trigger_action" % _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['ref']: + _path_params['ref'] = _params['ref'] + + if _params['action']: + _path_params['action'] = _params['action'] + + + # process the query parameters + _query_params = [] + # 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}/refs/{ref}/actions/{action}/triggers', 'POST', + _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')) diff --git a/clients/python/test/test_actions_api.py b/clients/python/test/test_actions_api.py index 9c3243f8752..20a3f0c9d02 100644 --- a/clients/python/test/test_actions_api.py +++ b/clients/python/test/test_actions_api.py @@ -55,6 +55,13 @@ def test_list_run_hooks(self) -> None: """ pass + def test_trigger_action(self) -> None: + """Test case for trigger_action + + manually trigger an action # noqa: E501 + """ + pass + if __name__ == '__main__': unittest.main() diff --git a/clients/rust/README.md b/clients/rust/README.md index 3d22a3379e9..07615c3ae49 100644 --- a/clients/rust/README.md +++ b/clients/rust/README.md @@ -30,6 +30,7 @@ Class | Method | HTTP request | Description *ActionsApi* | [**get_run_hook_output**](docs/ActionsApi.md#get_run_hook_output) | **GET** /repositories/{repository}/actions/runs/{run_id}/hooks/{hook_run_id}/output | get run hook output *ActionsApi* | [**list_repository_runs**](docs/ActionsApi.md#list_repository_runs) | **GET** /repositories/{repository}/actions/runs | list runs *ActionsApi* | [**list_run_hooks**](docs/ActionsApi.md#list_run_hooks) | **GET** /repositories/{repository}/actions/runs/{run_id}/hooks | list run hooks +*ActionsApi* | [**trigger_action**](docs/ActionsApi.md#trigger_action) | **POST** /repositories/{repository}/refs/{ref}/actions/{action}/triggers | manually trigger an action *AuthApi* | [**add_group_membership**](docs/AuthApi.md#add_group_membership) | **PUT** /auth/groups/{groupId}/members/{userId} | add group membership *AuthApi* | [**attach_policy_to_group**](docs/AuthApi.md#attach_policy_to_group) | **PUT** /auth/groups/{groupId}/policies/{policyId} | attach policy to group *AuthApi* | [**attach_policy_to_user**](docs/AuthApi.md#attach_policy_to_user) | **PUT** /auth/users/{userId}/policies/{policyId} | attach policy to user diff --git a/clients/rust/docs/ActionsApi.md b/clients/rust/docs/ActionsApi.md index 49100a3271d..f5dd7046e3a 100644 --- a/clients/rust/docs/ActionsApi.md +++ b/clients/rust/docs/ActionsApi.md @@ -8,6 +8,7 @@ Method | HTTP request | Description [**get_run_hook_output**](ActionsApi.md#get_run_hook_output) | **GET** /repositories/{repository}/actions/runs/{run_id}/hooks/{hook_run_id}/output | get run hook output [**list_repository_runs**](ActionsApi.md#list_repository_runs) | **GET** /repositories/{repository}/actions/runs | list runs [**list_run_hooks**](ActionsApi.md#list_run_hooks) | **GET** /repositories/{repository}/actions/runs/{run_id}/hooks | list run hooks +[**trigger_action**](ActionsApi.md#trigger_action) | **POST** /repositories/{repository}/refs/{ref}/actions/{action}/triggers | manually trigger an action @@ -132,3 +133,33 @@ Name | Type | Description | Required | Notes [[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) + +## trigger_action + +> trigger_action(repository, r#ref, action) +manually trigger an action + +### Parameters + + +Name | Type | Description | Required | Notes +------------- | ------------- | ------------- | ------------- | ------------- +**repository** | **String** | | [required] | +**r#ref** | **String** | | [required] | +**action** | **String** | | [required] | + +### Return type + + (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 + +[[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) + diff --git a/clients/rust/src/apis/actions_api.rs b/clients/rust/src/apis/actions_api.rs index eefcb6dbad2..81fdf6376d1 100644 --- a/clients/rust/src/apis/actions_api.rs +++ b/clients/rust/src/apis/actions_api.rs @@ -59,6 +59,19 @@ pub enum ListRunHooksError { UnknownValue(serde_json::Value), } +/// struct for typed errors of method [`trigger_action`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum TriggerActionError { + Status400(models::Error), + Status401(models::Error), + Status403(models::Error), + Status404(models::Error), + Status420(), + DefaultResponse(models::Error), + UnknownValue(serde_json::Value), +} + pub async fn get_run(configuration: &configuration::Configuration, repository: &str, run_id: &str) -> Result> { let local_var_configuration = configuration; @@ -210,3 +223,36 @@ pub async fn list_run_hooks(configuration: &configuration::Configuration, reposi } } +pub async fn trigger_action(configuration: &configuration::Configuration, repository: &str, r#ref: &str, action: &str) -> Result<(), Error> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!("{}/repositories/{repository}/refs/{ref}/actions/{action}/triggers", local_var_configuration.base_path, repository=crate::apis::urlencode(repository), ref=crate::apis::urlencode(r#ref), action=crate::apis::urlencode(action)); + let mut local_var_req_builder = local_var_client.request(reqwest::Method::POST, local_var_uri_str.as_str()); + + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + if let Some(ref local_var_auth_conf) = local_var_configuration.basic_auth { + local_var_req_builder = local_var_req_builder.basic_auth(local_var_auth_conf.0.to_owned(), local_var_auth_conf.1.to_owned()); + }; + if let Some(ref local_var_token) = local_var_configuration.bearer_access_token { + local_var_req_builder = local_var_req_builder.bearer_auth(local_var_token.to_owned()); + }; + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + Ok(()) + } else { + let local_var_entity: Option = serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { status: local_var_status, content: local_var_content, entity: local_var_entity }; + Err(Error::ResponseError(local_var_error)) + } +} +