fix(actions): align actions paths and add user variables
Some checks failed
Security Scan / security (push) Successful in 12s
Test / test (push) Has been cancelled

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-02 02:23:01 +00:00
parent 48c6296d25
commit dabfa7ee9d
2 changed files with 280 additions and 5 deletions

View file

@ -40,14 +40,14 @@ func (s *ActionsService) IterRepoSecrets(ctx context.Context, owner, repo string
// CreateRepoSecret creates or updates a secret in a repository. // CreateRepoSecret creates or updates a secret in a repository.
// Forgejo expects a PUT with {"data": "secret-value"} body. // Forgejo expects a PUT with {"data": "secret-value"} body.
func (s *ActionsService) CreateRepoSecret(ctx context.Context, owner, repo, name string, data string) error { func (s *ActionsService) CreateRepoSecret(ctx context.Context, owner, repo, name string, data string) error {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/secrets/{name}", pathParams("owner", owner, "repo", repo, "name", name)) path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/secrets/{secretname}", pathParams("owner", owner, "repo", repo, "secretname", name))
body := map[string]string{"data": data} body := map[string]string{"data": data}
return s.client.Put(ctx, path, body, nil) return s.client.Put(ctx, path, body, nil)
} }
// DeleteRepoSecret removes a secret from a repository. // DeleteRepoSecret removes a secret from a repository.
func (s *ActionsService) DeleteRepoSecret(ctx context.Context, owner, repo, name string) error { func (s *ActionsService) DeleteRepoSecret(ctx context.Context, owner, repo, name string) error {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/secrets/{name}", pathParams("owner", owner, "repo", repo, "name", name)) path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/secrets/{secretname}", pathParams("owner", owner, "repo", repo, "secretname", name))
return s.client.Delete(ctx, path) return s.client.Delete(ctx, path)
} }
@ -66,20 +66,20 @@ func (s *ActionsService) IterRepoVariables(ctx context.Context, owner, repo stri
// CreateRepoVariable creates a new action variable in a repository. // CreateRepoVariable creates a new action variable in a repository.
// Forgejo expects a POST with {"value": "var-value"} body. // Forgejo expects a POST with {"value": "var-value"} body.
func (s *ActionsService) CreateRepoVariable(ctx context.Context, owner, repo, name, value string) error { func (s *ActionsService) CreateRepoVariable(ctx context.Context, owner, repo, name, value string) error {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/variables/{name}", pathParams("owner", owner, "repo", repo, "name", name)) path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/variables/{variablename}", pathParams("owner", owner, "repo", repo, "variablename", name))
body := types.CreateVariableOption{Value: value} body := types.CreateVariableOption{Value: value}
return s.client.Post(ctx, path, body, nil) return s.client.Post(ctx, path, body, nil)
} }
// UpdateRepoVariable updates an existing action variable in a repository. // UpdateRepoVariable updates an existing action variable in a repository.
func (s *ActionsService) UpdateRepoVariable(ctx context.Context, owner, repo, name string, opts *types.UpdateVariableOption) error { func (s *ActionsService) UpdateRepoVariable(ctx context.Context, owner, repo, name string, opts *types.UpdateVariableOption) error {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/variables/{name}", pathParams("owner", owner, "repo", repo, "name", name)) path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/variables/{variablename}", pathParams("owner", owner, "repo", repo, "variablename", name))
return s.client.Put(ctx, path, opts, nil) return s.client.Put(ctx, path, opts, nil)
} }
// DeleteRepoVariable removes an action variable from a repository. // DeleteRepoVariable removes an action variable from a repository.
func (s *ActionsService) DeleteRepoVariable(ctx context.Context, owner, repo, name string) error { func (s *ActionsService) DeleteRepoVariable(ctx context.Context, owner, repo, name string) error {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/variables/{name}", pathParams("owner", owner, "repo", repo, "name", name)) path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/variables/{variablename}", pathParams("owner", owner, "repo", repo, "variablename", name))
return s.client.Delete(ctx, path) return s.client.Delete(ctx, path)
} }
@ -107,6 +107,100 @@ func (s *ActionsService) IterOrgVariables(ctx context.Context, org string) iter.
return ListIter[types.ActionVariable](ctx, s.client, path, nil) return ListIter[types.ActionVariable](ctx, s.client, path, nil)
} }
// GetOrgVariable returns a single action variable for an organisation.
func (s *ActionsService) GetOrgVariable(ctx context.Context, org, name string) (*types.ActionVariable, error) {
path := ResolvePath("/api/v1/orgs/{org}/actions/variables/{variablename}", pathParams("org", org, "variablename", name))
var out types.ActionVariable
if err := s.client.Get(ctx, path, &out); err != nil {
return nil, err
}
return &out, nil
}
// CreateOrgVariable creates a new action variable in an organisation.
func (s *ActionsService) CreateOrgVariable(ctx context.Context, org, name, value string) error {
path := ResolvePath("/api/v1/orgs/{org}/actions/variables/{variablename}", pathParams("org", org, "variablename", name))
body := types.CreateVariableOption{Value: value}
return s.client.Post(ctx, path, body, nil)
}
// UpdateOrgVariable updates an existing action variable in an organisation.
func (s *ActionsService) UpdateOrgVariable(ctx context.Context, org, name string, opts *types.UpdateVariableOption) error {
path := ResolvePath("/api/v1/orgs/{org}/actions/variables/{variablename}", pathParams("org", org, "variablename", name))
return s.client.Put(ctx, path, opts, nil)
}
// DeleteOrgVariable removes an action variable from an organisation.
func (s *ActionsService) DeleteOrgVariable(ctx context.Context, org, name string) error {
path := ResolvePath("/api/v1/orgs/{org}/actions/variables/{variablename}", pathParams("org", org, "variablename", name))
return s.client.Delete(ctx, path)
}
// CreateOrgSecret creates or updates a secret in an organisation.
func (s *ActionsService) CreateOrgSecret(ctx context.Context, org, name, data string) error {
path := ResolvePath("/api/v1/orgs/{org}/actions/secrets/{secretname}", pathParams("org", org, "secretname", name))
body := map[string]string{"data": data}
return s.client.Put(ctx, path, body, nil)
}
// DeleteOrgSecret removes a secret from an organisation.
func (s *ActionsService) DeleteOrgSecret(ctx context.Context, org, name string) error {
path := ResolvePath("/api/v1/orgs/{org}/actions/secrets/{secretname}", pathParams("org", org, "secretname", name))
return s.client.Delete(ctx, path)
}
// ListUserVariables returns all action variables for the authenticated user.
func (s *ActionsService) ListUserVariables(ctx context.Context) ([]types.ActionVariable, error) {
return ListAll[types.ActionVariable](ctx, s.client, "/api/v1/user/actions/variables", nil)
}
// IterUserVariables returns an iterator over all action variables for the authenticated user.
func (s *ActionsService) IterUserVariables(ctx context.Context) iter.Seq2[types.ActionVariable, error] {
return ListIter[types.ActionVariable](ctx, s.client, "/api/v1/user/actions/variables", nil)
}
// GetUserVariable returns a single action variable for the authenticated user.
func (s *ActionsService) GetUserVariable(ctx context.Context, name string) (*types.ActionVariable, error) {
path := ResolvePath("/api/v1/user/actions/variables/{variablename}", pathParams("variablename", name))
var out types.ActionVariable
if err := s.client.Get(ctx, path, &out); err != nil {
return nil, err
}
return &out, nil
}
// CreateUserVariable creates a new action variable for the authenticated user.
func (s *ActionsService) CreateUserVariable(ctx context.Context, name, value string) error {
path := ResolvePath("/api/v1/user/actions/variables/{variablename}", pathParams("variablename", name))
body := types.CreateVariableOption{Value: value}
return s.client.Post(ctx, path, body, nil)
}
// UpdateUserVariable updates an existing action variable for the authenticated user.
func (s *ActionsService) UpdateUserVariable(ctx context.Context, name string, opts *types.UpdateVariableOption) error {
path := ResolvePath("/api/v1/user/actions/variables/{variablename}", pathParams("variablename", name))
return s.client.Put(ctx, path, opts, nil)
}
// DeleteUserVariable removes an action variable for the authenticated user.
func (s *ActionsService) DeleteUserVariable(ctx context.Context, name string) error {
path := ResolvePath("/api/v1/user/actions/variables/{variablename}", pathParams("variablename", name))
return s.client.Delete(ctx, path)
}
// CreateUserSecret creates or updates a secret for the authenticated user.
func (s *ActionsService) CreateUserSecret(ctx context.Context, name, data string) error {
path := ResolvePath("/api/v1/user/actions/secrets/{secretname}", pathParams("secretname", name))
body := map[string]string{"data": data}
return s.client.Put(ctx, path, body, nil)
}
// DeleteUserSecret removes a secret for the authenticated user.
func (s *ActionsService) DeleteUserSecret(ctx context.Context, name string) error {
path := ResolvePath("/api/v1/user/actions/secrets/{secretname}", pathParams("secretname", name))
return s.client.Delete(ctx, path)
}
// DispatchWorkflow triggers a workflow run. // DispatchWorkflow triggers a workflow run.
func (s *ActionsService) DispatchWorkflow(ctx context.Context, owner, repo, workflow string, opts map[string]any) error { func (s *ActionsService) DispatchWorkflow(ctx context.Context, owner, repo, workflow string, opts map[string]any) error {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/workflows/{workflow}/dispatches", pathParams("owner", owner, "repo", repo, "workflow", workflow)) path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/workflows/{workflow}/dispatches", pathParams("owner", owner, "repo", repo, "workflow", workflow))

View file

@ -248,6 +248,187 @@ func TestActionsService_ListOrgVariables_Good(t *testing.T) {
} }
} }
func TestActionsService_GetOrgVariable_Good(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
t.Errorf("expected GET, got %s", r.Method)
}
if r.URL.Path != "/api/v1/orgs/lethean/actions/variables/ORG_VAR" {
t.Errorf("wrong path: %s", r.URL.Path)
}
json.NewEncoder(w).Encode(types.ActionVariable{Name: "ORG_VAR", Data: "org-value"})
}))
defer srv.Close()
f := NewForge(srv.URL, "tok")
variable, err := f.Actions.GetOrgVariable(context.Background(), "lethean", "ORG_VAR")
if err != nil {
t.Fatal(err)
}
if variable.Name != "ORG_VAR" || variable.Data != "org-value" {
t.Fatalf("unexpected variable: %#v", variable)
}
}
func TestActionsService_CreateUserVariable_Good(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
t.Errorf("expected POST, got %s", r.Method)
}
if r.URL.Path != "/api/v1/user/actions/variables/CI_ENV" {
t.Errorf("wrong path: %s", r.URL.Path)
}
var body types.CreateVariableOption
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
t.Fatal(err)
}
if body.Value != "production" {
t.Errorf("got value=%q, want %q", body.Value, "production")
}
w.WriteHeader(http.StatusCreated)
}))
defer srv.Close()
f := NewForge(srv.URL, "tok")
if err := f.Actions.CreateUserVariable(context.Background(), "CI_ENV", "production"); err != nil {
t.Fatal(err)
}
}
func TestActionsService_ListUserVariables_Good(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
t.Errorf("expected GET, got %s", r.Method)
}
if r.URL.Path != "/api/v1/user/actions/variables" {
t.Errorf("wrong path: %s", r.URL.Path)
}
w.Header().Set("X-Total-Count", "1")
json.NewEncoder(w).Encode([]types.ActionVariable{{Name: "CI_ENV", Data: "production"}})
}))
defer srv.Close()
f := NewForge(srv.URL, "tok")
vars, err := f.Actions.ListUserVariables(context.Background())
if err != nil {
t.Fatal(err)
}
if len(vars) != 1 || vars[0].Name != "CI_ENV" {
t.Fatalf("unexpected variables: %#v", vars)
}
}
func TestActionsService_GetUserVariable_Good(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
t.Errorf("expected GET, got %s", r.Method)
}
if r.URL.Path != "/api/v1/user/actions/variables/CI_ENV" {
t.Errorf("wrong path: %s", r.URL.Path)
}
json.NewEncoder(w).Encode(types.ActionVariable{Name: "CI_ENV", Data: "production"})
}))
defer srv.Close()
f := NewForge(srv.URL, "tok")
variable, err := f.Actions.GetUserVariable(context.Background(), "CI_ENV")
if err != nil {
t.Fatal(err)
}
if variable.Name != "CI_ENV" || variable.Data != "production" {
t.Fatalf("unexpected variable: %#v", variable)
}
}
func TestActionsService_UpdateUserVariable_Good(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPut {
t.Errorf("expected PUT, got %s", r.Method)
}
if r.URL.Path != "/api/v1/user/actions/variables/CI_ENV" {
t.Errorf("wrong path: %s", r.URL.Path)
}
var body types.UpdateVariableOption
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
t.Fatal(err)
}
if body.Name != "CI_ENV_NEW" || body.Value != "staging" {
t.Fatalf("unexpected body: %#v", body)
}
w.WriteHeader(http.StatusNoContent)
}))
defer srv.Close()
f := NewForge(srv.URL, "tok")
if err := f.Actions.UpdateUserVariable(context.Background(), "CI_ENV", &types.UpdateVariableOption{
Name: "CI_ENV_NEW",
Value: "staging",
}); err != nil {
t.Fatal(err)
}
}
func TestActionsService_DeleteUserVariable_Good(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodDelete {
t.Errorf("expected DELETE, got %s", r.Method)
}
if r.URL.Path != "/api/v1/user/actions/variables/OLD_VAR" {
t.Errorf("wrong path: %s", r.URL.Path)
}
w.WriteHeader(http.StatusNoContent)
}))
defer srv.Close()
f := NewForge(srv.URL, "tok")
if err := f.Actions.DeleteUserVariable(context.Background(), "OLD_VAR"); err != nil {
t.Fatal(err)
}
}
func TestActionsService_CreateUserSecret_Good(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPut {
t.Errorf("expected PUT, got %s", r.Method)
}
if r.URL.Path != "/api/v1/user/actions/secrets/DEPLOY_KEY" {
t.Errorf("wrong path: %s", r.URL.Path)
}
var body map[string]string
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
t.Fatal(err)
}
if body["data"] != "secret-value" {
t.Errorf("got data=%q, want %q", body["data"], "secret-value")
}
w.WriteHeader(http.StatusCreated)
}))
defer srv.Close()
f := NewForge(srv.URL, "tok")
if err := f.Actions.CreateUserSecret(context.Background(), "DEPLOY_KEY", "secret-value"); err != nil {
t.Fatal(err)
}
}
func TestActionsService_DeleteUserSecret_Good(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodDelete {
t.Errorf("expected DELETE, got %s", r.Method)
}
if r.URL.Path != "/api/v1/user/actions/secrets/OLD_KEY" {
t.Errorf("wrong path: %s", r.URL.Path)
}
w.WriteHeader(http.StatusNoContent)
}))
defer srv.Close()
f := NewForge(srv.URL, "tok")
if err := f.Actions.DeleteUserSecret(context.Background(), "OLD_KEY"); err != nil {
t.Fatal(err)
}
}
func TestActionsService_DispatchWorkflow_Good(t *testing.T) { func TestActionsService_DispatchWorkflow_Good(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost { if r.Method != http.MethodPost {