| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694 |
- // Copyright 2023 The Gitea Authors. All rights reserved.
- // SPDX-License-Identifier: MIT
-
- package repo
-
- import (
- go_context "context"
- "crypto/hmac"
- "crypto/sha256"
- "encoding/base64"
- "errors"
- "fmt"
- "net/http"
- "net/url"
- "strconv"
- "strings"
- "time"
-
- actions_model "code.gitea.io/gitea/models/actions"
- "code.gitea.io/gitea/models/db"
- repo_model "code.gitea.io/gitea/models/repo"
- secret_model "code.gitea.io/gitea/models/secret"
- "code.gitea.io/gitea/modules/actions"
- "code.gitea.io/gitea/modules/httplib"
- "code.gitea.io/gitea/modules/setting"
- api "code.gitea.io/gitea/modules/structs"
- "code.gitea.io/gitea/modules/util"
- "code.gitea.io/gitea/modules/web"
- "code.gitea.io/gitea/routers/api/v1/shared"
- "code.gitea.io/gitea/routers/api/v1/utils"
- actions_service "code.gitea.io/gitea/services/actions"
- "code.gitea.io/gitea/services/context"
- "code.gitea.io/gitea/services/convert"
- secret_service "code.gitea.io/gitea/services/secrets"
-
- "github.com/nektos/act/pkg/model"
- )
-
- // ListActionsSecrets list an repo's actions secrets
- func (Action) ListActionsSecrets(ctx *context.APIContext) {
- // swagger:operation GET /repos/{owner}/{repo}/actions/secrets repository repoListActionsSecrets
- // ---
- // summary: List an repo's actions secrets
- // produces:
- // - application/json
- // parameters:
- // - name: owner
- // in: path
- // description: owner of the repo
- // type: string
- // required: true
- // - name: repo
- // in: path
- // description: name of the repository
- // type: string
- // required: true
- // - name: page
- // in: query
- // description: page number of results to return (1-based)
- // type: integer
- // - name: limit
- // in: query
- // description: page size of results
- // type: integer
- // responses:
- // "200":
- // "$ref": "#/responses/SecretList"
- // "404":
- // "$ref": "#/responses/notFound"
-
- repo := ctx.Repo.Repository
-
- opts := &secret_model.FindSecretsOptions{
- RepoID: repo.ID,
- ListOptions: utils.GetListOptions(ctx),
- }
-
- secrets, count, err := db.FindAndCount[secret_model.Secret](ctx, opts)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
-
- apiSecrets := make([]*api.Secret, len(secrets))
- for k, v := range secrets {
- apiSecrets[k] = &api.Secret{
- Name: v.Name,
- Description: v.Description,
- Created: v.CreatedUnix.AsTime(),
- }
- }
-
- ctx.SetTotalCountHeader(count)
- ctx.JSON(http.StatusOK, apiSecrets)
- }
-
- // create or update one secret of the repository
- func (Action) CreateOrUpdateSecret(ctx *context.APIContext) {
- // swagger:operation PUT /repos/{owner}/{repo}/actions/secrets/{secretname} repository updateRepoSecret
- // ---
- // summary: Create or Update a secret value in a repository
- // consumes:
- // - application/json
- // produces:
- // - application/json
- // parameters:
- // - name: owner
- // in: path
- // description: owner of the repository
- // type: string
- // required: true
- // - name: repo
- // in: path
- // description: name of the repository
- // type: string
- // required: true
- // - name: secretname
- // in: path
- // description: name of the secret
- // type: string
- // required: true
- // - name: body
- // in: body
- // schema:
- // "$ref": "#/definitions/CreateOrUpdateSecretOption"
- // responses:
- // "201":
- // description: response when creating a secret
- // "204":
- // description: response when updating a secret
- // "400":
- // "$ref": "#/responses/error"
- // "404":
- // "$ref": "#/responses/notFound"
-
- repo := ctx.Repo.Repository
-
- opt := web.GetForm(ctx).(*api.CreateOrUpdateSecretOption)
-
- _, created, err := secret_service.CreateOrUpdateSecret(ctx, 0, repo.ID, ctx.PathParam("secretname"), opt.Data, opt.Description)
- if err != nil {
- if errors.Is(err, util.ErrInvalidArgument) {
- ctx.APIError(http.StatusBadRequest, err)
- } else if errors.Is(err, util.ErrNotExist) {
- ctx.APIError(http.StatusNotFound, err)
- } else {
- ctx.APIErrorInternal(err)
- }
- return
- }
-
- if created {
- ctx.Status(http.StatusCreated)
- } else {
- ctx.Status(http.StatusNoContent)
- }
- }
-
- // DeleteSecret delete one secret of the repository
- func (Action) DeleteSecret(ctx *context.APIContext) {
- // swagger:operation DELETE /repos/{owner}/{repo}/actions/secrets/{secretname} repository deleteRepoSecret
- // ---
- // summary: Delete a secret in a repository
- // consumes:
- // - application/json
- // produces:
- // - application/json
- // parameters:
- // - name: owner
- // in: path
- // description: owner of the repository
- // type: string
- // required: true
- // - name: repo
- // in: path
- // description: name of the repository
- // type: string
- // required: true
- // - name: secretname
- // in: path
- // description: name of the secret
- // type: string
- // required: true
- // responses:
- // "204":
- // description: delete one secret of the repository
- // "400":
- // "$ref": "#/responses/error"
- // "404":
- // "$ref": "#/responses/notFound"
-
- repo := ctx.Repo.Repository
-
- err := secret_service.DeleteSecretByName(ctx, 0, repo.ID, ctx.PathParam("secretname"))
- if err != nil {
- if errors.Is(err, util.ErrInvalidArgument) {
- ctx.APIError(http.StatusBadRequest, err)
- } else if errors.Is(err, util.ErrNotExist) {
- ctx.APIError(http.StatusNotFound, err)
- } else {
- ctx.APIErrorInternal(err)
- }
- return
- }
-
- ctx.Status(http.StatusNoContent)
- }
-
- // GetVariable get a repo-level variable
- func (Action) GetVariable(ctx *context.APIContext) {
- // swagger:operation GET /repos/{owner}/{repo}/actions/variables/{variablename} repository getRepoVariable
- // ---
- // summary: Get a repo-level variable
- // produces:
- // - application/json
- // parameters:
- // - name: owner
- // in: path
- // description: owner of the repo
- // type: string
- // required: true
- // - name: repo
- // in: path
- // description: name of the repository
- // type: string
- // required: true
- // - name: variablename
- // in: path
- // description: name of the variable
- // type: string
- // required: true
- // responses:
- // "200":
- // "$ref": "#/responses/ActionVariable"
- // "400":
- // "$ref": "#/responses/error"
- // "404":
- // "$ref": "#/responses/notFound"
- v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
- RepoID: ctx.Repo.Repository.ID,
- Name: ctx.PathParam("variablename"),
- })
- if err != nil {
- if errors.Is(err, util.ErrNotExist) {
- ctx.APIError(http.StatusNotFound, err)
- } else {
- ctx.APIErrorInternal(err)
- }
- return
- }
-
- variable := &api.ActionVariable{
- OwnerID: v.OwnerID,
- RepoID: v.RepoID,
- Name: v.Name,
- Data: v.Data,
- Description: v.Description,
- }
-
- ctx.JSON(http.StatusOK, variable)
- }
-
- // DeleteVariable delete a repo-level variable
- func (Action) DeleteVariable(ctx *context.APIContext) {
- // swagger:operation DELETE /repos/{owner}/{repo}/actions/variables/{variablename} repository deleteRepoVariable
- // ---
- // summary: Delete a repo-level variable
- // produces:
- // - application/json
- // parameters:
- // - name: owner
- // in: path
- // description: owner of the repo
- // type: string
- // required: true
- // - name: repo
- // in: path
- // description: name of the repository
- // type: string
- // required: true
- // - name: variablename
- // in: path
- // description: name of the variable
- // type: string
- // required: true
- // responses:
- // "200":
- // "$ref": "#/responses/ActionVariable"
- // "201":
- // description: response when deleting a variable
- // "204":
- // description: response when deleting a variable
- // "400":
- // "$ref": "#/responses/error"
- // "404":
- // "$ref": "#/responses/notFound"
-
- if err := actions_service.DeleteVariableByName(ctx, 0, ctx.Repo.Repository.ID, ctx.PathParam("variablename")); err != nil {
- if errors.Is(err, util.ErrInvalidArgument) {
- ctx.APIError(http.StatusBadRequest, err)
- } else if errors.Is(err, util.ErrNotExist) {
- ctx.APIError(http.StatusNotFound, err)
- } else {
- ctx.APIErrorInternal(err)
- }
- return
- }
-
- ctx.Status(http.StatusNoContent)
- }
-
- // CreateVariable create a repo-level variable
- func (Action) CreateVariable(ctx *context.APIContext) {
- // swagger:operation POST /repos/{owner}/{repo}/actions/variables/{variablename} repository createRepoVariable
- // ---
- // summary: Create a repo-level variable
- // produces:
- // - application/json
- // parameters:
- // - name: owner
- // in: path
- // description: owner of the repo
- // type: string
- // required: true
- // - name: repo
- // in: path
- // description: name of the repository
- // type: string
- // required: true
- // - name: variablename
- // in: path
- // description: name of the variable
- // type: string
- // required: true
- // - name: body
- // in: body
- // schema:
- // "$ref": "#/definitions/CreateVariableOption"
- // responses:
- // "201":
- // description: response when creating a repo-level variable
- // "400":
- // "$ref": "#/responses/error"
- // "409":
- // description: variable name already exists.
- // "500":
- // "$ref": "#/responses/error"
-
- opt := web.GetForm(ctx).(*api.CreateVariableOption)
-
- repoID := ctx.Repo.Repository.ID
- variableName := ctx.PathParam("variablename")
-
- v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
- RepoID: repoID,
- Name: variableName,
- })
- if err != nil && !errors.Is(err, util.ErrNotExist) {
- ctx.APIErrorInternal(err)
- return
- }
- if v != nil && v.ID > 0 {
- ctx.APIError(http.StatusConflict, util.NewAlreadyExistErrorf("variable name %s already exists", variableName))
- return
- }
-
- if _, err := actions_service.CreateVariable(ctx, 0, repoID, variableName, opt.Value, opt.Description); err != nil {
- if errors.Is(err, util.ErrInvalidArgument) {
- ctx.APIError(http.StatusBadRequest, err)
- } else {
- ctx.APIErrorInternal(err)
- }
- return
- }
-
- ctx.Status(http.StatusCreated)
- }
-
- // UpdateVariable update a repo-level variable
- func (Action) UpdateVariable(ctx *context.APIContext) {
- // swagger:operation PUT /repos/{owner}/{repo}/actions/variables/{variablename} repository updateRepoVariable
- // ---
- // summary: Update a repo-level variable
- // produces:
- // - application/json
- // parameters:
- // - name: owner
- // in: path
- // description: owner of the repo
- // type: string
- // required: true
- // - name: repo
- // in: path
- // description: name of the repository
- // type: string
- // required: true
- // - name: variablename
- // in: path
- // description: name of the variable
- // type: string
- // required: true
- // - name: body
- // in: body
- // schema:
- // "$ref": "#/definitions/UpdateVariableOption"
- // responses:
- // "201":
- // description: response when updating a repo-level variable
- // "204":
- // description: response when updating a repo-level variable
- // "400":
- // "$ref": "#/responses/error"
- // "404":
- // "$ref": "#/responses/notFound"
-
- opt := web.GetForm(ctx).(*api.UpdateVariableOption)
-
- v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
- RepoID: ctx.Repo.Repository.ID,
- Name: ctx.PathParam("variablename"),
- })
- if err != nil {
- if errors.Is(err, util.ErrNotExist) {
- ctx.APIError(http.StatusNotFound, err)
- } else {
- ctx.APIErrorInternal(err)
- }
- return
- }
-
- if opt.Name == "" {
- opt.Name = ctx.PathParam("variablename")
- }
-
- v.Name = opt.Name
- v.Data = opt.Value
- v.Description = opt.Description
-
- if _, err := actions_service.UpdateVariableNameData(ctx, v); err != nil {
- if errors.Is(err, util.ErrInvalidArgument) {
- ctx.APIError(http.StatusBadRequest, err)
- } else {
- ctx.APIErrorInternal(err)
- }
- return
- }
-
- ctx.Status(http.StatusNoContent)
- }
-
- // ListVariables list repo-level variables
- func (Action) ListVariables(ctx *context.APIContext) {
- // swagger:operation GET /repos/{owner}/{repo}/actions/variables repository getRepoVariablesList
- // ---
- // summary: Get repo-level variables list
- // produces:
- // - application/json
- // parameters:
- // - name: owner
- // in: path
- // description: owner of the repo
- // type: string
- // required: true
- // - name: repo
- // in: path
- // description: name of the repository
- // type: string
- // required: true
- // - name: page
- // in: query
- // description: page number of results to return (1-based)
- // type: integer
- // - name: limit
- // in: query
- // description: page size of results
- // type: integer
- // responses:
- // "200":
- // "$ref": "#/responses/VariableList"
- // "400":
- // "$ref": "#/responses/error"
- // "404":
- // "$ref": "#/responses/notFound"
-
- vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{
- RepoID: ctx.Repo.Repository.ID,
- ListOptions: utils.GetListOptions(ctx),
- })
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
-
- variables := make([]*api.ActionVariable, len(vars))
- for i, v := range vars {
- variables[i] = &api.ActionVariable{
- OwnerID: v.OwnerID,
- RepoID: v.RepoID,
- Name: v.Name,
- Data: v.Data,
- Description: v.Description,
- }
- }
-
- ctx.SetTotalCountHeader(count)
- ctx.JSON(http.StatusOK, variables)
- }
-
- // GetRegistrationToken returns the token to register repo runners
- func (Action) GetRegistrationToken(ctx *context.APIContext) {
- // swagger:operation GET /repos/{owner}/{repo}/actions/runners/registration-token repository repoGetRunnerRegistrationToken
- // ---
- // summary: Get a repository's actions runner registration token
- // produces:
- // - application/json
- // parameters:
- // - name: owner
- // in: path
- // description: owner of the repo
- // type: string
- // required: true
- // - name: repo
- // in: path
- // description: name of the repo
- // type: string
- // required: true
- // responses:
- // "200":
- // "$ref": "#/responses/RegistrationToken"
-
- shared.GetRegistrationToken(ctx, 0, ctx.Repo.Repository.ID)
- }
-
- // CreateRegistrationToken returns the token to register repo runners
- func (Action) CreateRegistrationToken(ctx *context.APIContext) {
- // swagger:operation POST /repos/{owner}/{repo}/actions/runners/registration-token repository repoCreateRunnerRegistrationToken
- // ---
- // summary: Get a repository's actions runner registration token
- // produces:
- // - application/json
- // parameters:
- // - name: owner
- // in: path
- // description: owner of the repo
- // type: string
- // required: true
- // - name: repo
- // in: path
- // description: name of the repo
- // type: string
- // required: true
- // responses:
- // "200":
- // "$ref": "#/responses/RegistrationToken"
-
- shared.GetRegistrationToken(ctx, 0, ctx.Repo.Repository.ID)
- }
-
- // ListRunners get repo-level runners
- func (Action) ListRunners(ctx *context.APIContext) {
- // swagger:operation GET /repos/{owner}/{repo}/actions/runners repository getRepoRunners
- // ---
- // summary: Get repo-level runners
- // produces:
- // - application/json
- // parameters:
- // - name: owner
- // in: path
- // description: owner of the repo
- // type: string
- // required: true
- // - name: repo
- // in: path
- // description: name of the repo
- // type: string
- // required: true
- // responses:
- // "200":
- // "$ref": "#/definitions/ActionRunnersResponse"
- // "400":
- // "$ref": "#/responses/error"
- // "404":
- // "$ref": "#/responses/notFound"
- shared.ListRunners(ctx, 0, ctx.Repo.Repository.ID)
- }
-
- // GetRunner get an repo-level runner
- func (Action) GetRunner(ctx *context.APIContext) {
- // swagger:operation GET /repos/{owner}/{repo}/actions/runners/{runner_id} repository getRepoRunner
- // ---
- // summary: Get an repo-level runner
- // produces:
- // - application/json
- // parameters:
- // - name: owner
- // in: path
- // description: owner of the repo
- // type: string
- // required: true
- // - name: repo
- // in: path
- // description: name of the repo
- // type: string
- // required: true
- // - name: runner_id
- // in: path
- // description: id of the runner
- // type: string
- // required: true
- // responses:
- // "200":
- // "$ref": "#/definitions/ActionRunner"
- // "400":
- // "$ref": "#/responses/error"
- // "404":
- // "$ref": "#/responses/notFound"
- shared.GetRunner(ctx, 0, ctx.Repo.Repository.ID, ctx.PathParamInt64("runner_id"))
- }
-
- // DeleteRunner delete an repo-level runner
- func (Action) DeleteRunner(ctx *context.APIContext) {
- // swagger:operation DELETE /repos/{owner}/{repo}/actions/runners/{runner_id} repository deleteRepoRunner
- // ---
- // summary: Delete an repo-level runner
- // produces:
- // - application/json
- // parameters:
- // - name: owner
- // in: path
- // description: owner of the repo
- // type: string
- // required: true
- // - name: repo
- // in: path
- // description: name of the repo
- // type: string
- // required: true
- // - name: runner_id
- // in: path
- // description: id of the runner
- // type: string
- // required: true
- // responses:
- // "204":
- // description: runner has been deleted
- // "400":
- // "$ref": "#/responses/error"
- // "404":
- // "$ref": "#/responses/notFound"
- shared.DeleteRunner(ctx, 0, ctx.Repo.Repository.ID, ctx.PathParamInt64("runner_id"))
- }
-
- // GetWorkflowRunJobs Lists all jobs for a workflow run.
- func (Action) ListWorkflowJobs(ctx *context.APIContext) {
- // swagger:operation GET /repos/{owner}/{repo}/actions/jobs repository listWorkflowJobs
- // ---
- // summary: Lists all jobs for a repository
- // produces:
- // - application/json
- // parameters:
- // - name: owner
- // in: path
- // description: owner of the repo
- // type: string
- // required: true
- // - name: repo
- // in: path
- // description: name of the repository
- // type: string
- // required: true
- // - name: status
- // in: query
- // description: workflow status (pending, queued, in_progress, failure, success, skipped)
- // type: string
- // required: false
- // - name: page
- // in: query
- // description: page number of results to return (1-based)
- // type: integer
- // - name: limit
- // in: query
- // description: page size of results
- // type: integer
- // responses:
- // "200":
- // "$ref": "#/responses/WorkflowJobsList"
- // "400":
- // "$ref": "#/responses/error"
- // "404":
- // "$ref": "#/responses/notFound"
-
- repoID := ctx.Repo.Repository.ID
-
- shared.ListJobs(ctx, 0, repoID, 0)
- }
-
- // ListWorkflowRuns Lists all runs for a repository run.
- func (Action) ListWorkflowRuns(ctx *context.APIContext) {
- // swagger:operation GET /repos/{owner}/{repo}/actions/runs repository getWorkflowRuns
- // ---
- // summary: Lists all runs for a repository run
- // produces:
- // - application/json
- // parameters:
- // - name: owner
- // in: path
- // description: owner of the repo
- // type: string
- // required: true
- // - name: repo
- // in: path
- // description: name of the repository
- // type: string
- // required: true
- // - name: event
- // in: query
- // description: workflow event name
- // type: string
- // required: false
- // - name: branch
- // in: query
- // description: workflow branch
- // type: string
- // required: false
- // - name: status
- // in: query
- // description: workflow status (pending, queued, in_progress, failure, success, skipped)
- // type: string
- // required: false
- // - name: actor
- // in: query
- // description: triggered by user
- // type: string
- // required: false
- // - name: head_sha
- // in: query
- // description: triggering sha of the workflow run
- // type: string
- // required: false
- // - name: page
- // in: query
- // description: page number of results to return (1-based)
- // type: integer
- // - name: limit
- // in: query
- // description: page size of results
- // type: integer
- // responses:
- // "200":
- // "$ref": "#/responses/WorkflowRunsList"
- // "400":
- // "$ref": "#/responses/error"
- // "404":
- // "$ref": "#/responses/notFound"
-
- repoID := ctx.Repo.Repository.ID
-
- shared.ListRuns(ctx, 0, repoID)
- }
-
- var _ actions_service.API = new(Action)
-
- // Action implements actions_service.API
- type Action struct{}
-
- // NewAction creates a new Action service
- func NewAction() actions_service.API {
- return Action{}
- }
-
- // ListActionTasks list all the actions of a repository
- func ListActionTasks(ctx *context.APIContext) {
- // swagger:operation GET /repos/{owner}/{repo}/actions/tasks repository ListActionTasks
- // ---
- // summary: List a repository's action tasks
- // produces:
- // - application/json
- // parameters:
- // - name: owner
- // in: path
- // description: owner of the repo
- // type: string
- // required: true
- // - name: repo
- // in: path
- // description: name of the repo
- // type: string
- // required: true
- // - name: page
- // in: query
- // description: page number of results to return (1-based)
- // type: integer
- // - name: limit
- // in: query
- // description: page size of results, default maximum page size is 50
- // type: integer
- // responses:
- // "200":
- // "$ref": "#/responses/TasksList"
- // "400":
- // "$ref": "#/responses/error"
- // "403":
- // "$ref": "#/responses/forbidden"
- // "404":
- // "$ref": "#/responses/notFound"
- // "409":
- // "$ref": "#/responses/conflict"
- // "422":
- // "$ref": "#/responses/validationError"
-
- tasks, total, err := db.FindAndCount[actions_model.ActionTask](ctx, &actions_model.FindTaskOptions{
- ListOptions: utils.GetListOptions(ctx),
- RepoID: ctx.Repo.Repository.ID,
- })
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
-
- res := new(api.ActionTaskResponse)
- res.TotalCount = total
-
- res.Entries = make([]*api.ActionTask, len(tasks))
- for i := range tasks {
- convertedTask, err := convert.ToActionTask(ctx, tasks[i])
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
- res.Entries[i] = convertedTask
- }
-
- ctx.JSON(http.StatusOK, &res)
- }
-
- func ActionsListRepositoryWorkflows(ctx *context.APIContext) {
- // swagger:operation GET /repos/{owner}/{repo}/actions/workflows repository ActionsListRepositoryWorkflows
- // ---
- // summary: List repository workflows
- // produces:
- // - application/json
- // parameters:
- // - name: owner
- // in: path
- // description: owner of the repo
- // type: string
- // required: true
- // - name: repo
- // in: path
- // description: name of the repo
- // type: string
- // required: true
- // responses:
- // "200":
- // "$ref": "#/responses/ActionWorkflowList"
- // "400":
- // "$ref": "#/responses/error"
- // "403":
- // "$ref": "#/responses/forbidden"
- // "404":
- // "$ref": "#/responses/notFound"
- // "422":
- // "$ref": "#/responses/validationError"
- // "500":
- // "$ref": "#/responses/error"
-
- workflows, err := convert.ListActionWorkflows(ctx, ctx.Repo.GitRepo, ctx.Repo.Repository)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
-
- ctx.JSON(http.StatusOK, &api.ActionWorkflowResponse{Workflows: workflows, TotalCount: int64(len(workflows))})
- }
-
- func ActionsGetWorkflow(ctx *context.APIContext) {
- // swagger:operation GET /repos/{owner}/{repo}/actions/workflows/{workflow_id} repository ActionsGetWorkflow
- // ---
- // summary: Get a workflow
- // produces:
- // - application/json
- // parameters:
- // - name: owner
- // in: path
- // description: owner of the repo
- // type: string
- // required: true
- // - name: repo
- // in: path
- // description: name of the repo
- // type: string
- // required: true
- // - name: workflow_id
- // in: path
- // description: id of the workflow
- // type: string
- // required: true
- // responses:
- // "200":
- // "$ref": "#/responses/ActionWorkflow"
- // "400":
- // "$ref": "#/responses/error"
- // "403":
- // "$ref": "#/responses/forbidden"
- // "404":
- // "$ref": "#/responses/notFound"
- // "422":
- // "$ref": "#/responses/validationError"
- // "500":
- // "$ref": "#/responses/error"
-
- workflowID := ctx.PathParam("workflow_id")
- workflow, err := convert.GetActionWorkflow(ctx, ctx.Repo.GitRepo, ctx.Repo.Repository, workflowID)
- if err != nil {
- if errors.Is(err, util.ErrNotExist) {
- ctx.APIError(http.StatusNotFound, err)
- } else {
- ctx.APIErrorInternal(err)
- }
- return
- }
-
- ctx.JSON(http.StatusOK, workflow)
- }
-
- func ActionsDisableWorkflow(ctx *context.APIContext) {
- // swagger:operation PUT /repos/{owner}/{repo}/actions/workflows/{workflow_id}/disable repository ActionsDisableWorkflow
- // ---
- // summary: Disable a workflow
- // produces:
- // - application/json
- // parameters:
- // - name: owner
- // in: path
- // description: owner of the repo
- // type: string
- // required: true
- // - name: repo
- // in: path
- // description: name of the repo
- // type: string
- // required: true
- // - name: workflow_id
- // in: path
- // description: id of the workflow
- // type: string
- // required: true
- // responses:
- // "204":
- // description: No Content
- // "400":
- // "$ref": "#/responses/error"
- // "403":
- // "$ref": "#/responses/forbidden"
- // "404":
- // "$ref": "#/responses/notFound"
- // "422":
- // "$ref": "#/responses/validationError"
-
- workflowID := ctx.PathParam("workflow_id")
- err := actions_service.EnableOrDisableWorkflow(ctx, workflowID, false)
- if err != nil {
- if errors.Is(err, util.ErrNotExist) {
- ctx.APIError(http.StatusNotFound, err)
- } else {
- ctx.APIErrorInternal(err)
- }
- return
- }
-
- ctx.Status(http.StatusNoContent)
- }
-
- func ActionsDispatchWorkflow(ctx *context.APIContext) {
- // swagger:operation POST /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches repository ActionsDispatchWorkflow
- // ---
- // summary: Create a workflow dispatch event
- // produces:
- // - application/json
- // parameters:
- // - name: owner
- // in: path
- // description: owner of the repo
- // type: string
- // required: true
- // - name: repo
- // in: path
- // description: name of the repo
- // type: string
- // required: true
- // - name: workflow_id
- // in: path
- // description: id of the workflow
- // type: string
- // required: true
- // - name: body
- // in: body
- // schema:
- // "$ref": "#/definitions/CreateActionWorkflowDispatch"
- // responses:
- // "204":
- // description: No Content
- // "400":
- // "$ref": "#/responses/error"
- // "403":
- // "$ref": "#/responses/forbidden"
- // "404":
- // "$ref": "#/responses/notFound"
- // "422":
- // "$ref": "#/responses/validationError"
-
- workflowID := ctx.PathParam("workflow_id")
- opt := web.GetForm(ctx).(*api.CreateActionWorkflowDispatch)
- if opt.Ref == "" {
- ctx.APIError(http.StatusUnprocessableEntity, util.NewInvalidArgumentErrorf("ref is required parameter"))
- return
- }
-
- err := actions_service.DispatchActionWorkflow(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, workflowID, opt.Ref, func(workflowDispatch *model.WorkflowDispatch, inputs map[string]any) error {
- if strings.Contains(ctx.Req.Header.Get("Content-Type"), "form-urlencoded") {
- // The chi framework's "Binding" doesn't support to bind the form map values into a map[string]string
- // So we have to manually read the `inputs[key]` from the form
- for name, config := range workflowDispatch.Inputs {
- value := ctx.FormString("inputs["+name+"]", config.Default)
- inputs[name] = value
- }
- } else {
- for name, config := range workflowDispatch.Inputs {
- value, ok := opt.Inputs[name]
- if ok {
- inputs[name] = value
- } else {
- inputs[name] = config.Default
- }
- }
- }
- return nil
- })
- if err != nil {
- if errors.Is(err, util.ErrNotExist) {
- ctx.APIError(http.StatusNotFound, err)
- } else if errors.Is(err, util.ErrPermissionDenied) {
- ctx.APIError(http.StatusForbidden, err)
- } else {
- ctx.APIErrorInternal(err)
- }
- return
- }
-
- ctx.Status(http.StatusNoContent)
- }
-
- func ActionsEnableWorkflow(ctx *context.APIContext) {
- // swagger:operation PUT /repos/{owner}/{repo}/actions/workflows/{workflow_id}/enable repository ActionsEnableWorkflow
- // ---
- // summary: Enable a workflow
- // produces:
- // - application/json
- // parameters:
- // - name: owner
- // in: path
- // description: owner of the repo
- // type: string
- // required: true
- // - name: repo
- // in: path
- // description: name of the repo
- // type: string
- // required: true
- // - name: workflow_id
- // in: path
- // description: id of the workflow
- // type: string
- // required: true
- // responses:
- // "204":
- // description: No Content
- // "400":
- // "$ref": "#/responses/error"
- // "403":
- // "$ref": "#/responses/forbidden"
- // "404":
- // "$ref": "#/responses/notFound"
- // "409":
- // "$ref": "#/responses/conflict"
- // "422":
- // "$ref": "#/responses/validationError"
-
- workflowID := ctx.PathParam("workflow_id")
- err := actions_service.EnableOrDisableWorkflow(ctx, workflowID, true)
- if err != nil {
- if errors.Is(err, util.ErrNotExist) {
- ctx.APIError(http.StatusNotFound, err)
- } else {
- ctx.APIErrorInternal(err)
- }
- return
- }
-
- ctx.Status(http.StatusNoContent)
- }
-
- // GetWorkflowRun Gets a specific workflow run.
- func GetWorkflowRun(ctx *context.APIContext) {
- // swagger:operation GET /repos/{owner}/{repo}/actions/runs/{run} repository GetWorkflowRun
- // ---
- // summary: Gets a specific workflow run
- // produces:
- // - application/json
- // parameters:
- // - name: owner
- // in: path
- // description: owner of the repo
- // type: string
- // required: true
- // - name: repo
- // in: path
- // description: name of the repository
- // type: string
- // required: true
- // - name: run
- // in: path
- // description: id of the run
- // type: string
- // required: true
- // responses:
- // "200":
- // "$ref": "#/responses/WorkflowRun"
- // "400":
- // "$ref": "#/responses/error"
- // "404":
- // "$ref": "#/responses/notFound"
-
- runID := ctx.PathParamInt64("run")
- job, has, err := db.GetByID[actions_model.ActionRun](ctx, runID)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
-
- if !has || job.RepoID != ctx.Repo.Repository.ID {
- ctx.APIErrorNotFound(util.ErrNotExist)
- return
- }
-
- convertedRun, err := convert.ToActionWorkflowRun(ctx, ctx.Repo.Repository, job)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
- ctx.JSON(http.StatusOK, convertedRun)
- }
-
- // ListWorkflowRunJobs Lists all jobs for a workflow run.
- func ListWorkflowRunJobs(ctx *context.APIContext) {
- // swagger:operation GET /repos/{owner}/{repo}/actions/runs/{run}/jobs repository listWorkflowRunJobs
- // ---
- // summary: Lists all jobs for a workflow run
- // produces:
- // - application/json
- // parameters:
- // - name: owner
- // in: path
- // description: owner of the repo
- // type: string
- // required: true
- // - name: repo
- // in: path
- // description: name of the repository
- // type: string
- // required: true
- // - name: run
- // in: path
- // description: runid of the workflow run
- // type: integer
- // required: true
- // - name: status
- // in: query
- // description: workflow status (pending, queued, in_progress, failure, success, skipped)
- // type: string
- // required: false
- // - name: page
- // in: query
- // description: page number of results to return (1-based)
- // type: integer
- // - name: limit
- // in: query
- // description: page size of results
- // type: integer
- // responses:
- // "200":
- // "$ref": "#/responses/WorkflowJobsList"
- // "400":
- // "$ref": "#/responses/error"
- // "404":
- // "$ref": "#/responses/notFound"
-
- repoID := ctx.Repo.Repository.ID
-
- runID := ctx.PathParamInt64("run")
-
- // Avoid the list all jobs functionality for this api route to be used with a runID == 0.
- if runID <= 0 {
- ctx.APIError(http.StatusBadRequest, util.NewInvalidArgumentErrorf("runID must be a positive integer"))
- return
- }
-
- // runID is used as an additional filter next to repoID to ensure that we only list jobs for the specified repoID and runID.
- // no additional checks for runID are needed here
- shared.ListJobs(ctx, 0, repoID, runID)
- }
-
- // GetWorkflowJob Gets a specific workflow job for a workflow run.
- func GetWorkflowJob(ctx *context.APIContext) {
- // swagger:operation GET /repos/{owner}/{repo}/actions/jobs/{job_id} repository getWorkflowJob
- // ---
- // summary: Gets a specific workflow job for a workflow run
- // produces:
- // - application/json
- // parameters:
- // - name: owner
- // in: path
- // description: owner of the repo
- // type: string
- // required: true
- // - name: repo
- // in: path
- // description: name of the repository
- // type: string
- // required: true
- // - name: job_id
- // in: path
- // description: id of the job
- // type: string
- // required: true
- // responses:
- // "200":
- // "$ref": "#/responses/WorkflowJob"
- // "400":
- // "$ref": "#/responses/error"
- // "404":
- // "$ref": "#/responses/notFound"
-
- jobID := ctx.PathParamInt64("job_id")
- job, has, err := db.GetByID[actions_model.ActionRunJob](ctx, jobID)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
-
- if !has || job.RepoID != ctx.Repo.Repository.ID {
- ctx.APIErrorNotFound(util.ErrNotExist)
- return
- }
-
- convertedWorkflowJob, err := convert.ToActionWorkflowJob(ctx, ctx.Repo.Repository, nil, job)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
- ctx.JSON(http.StatusOK, convertedWorkflowJob)
- }
-
- // GetArtifactsOfRun Lists all artifacts for a repository.
- func GetArtifactsOfRun(ctx *context.APIContext) {
- // swagger:operation GET /repos/{owner}/{repo}/actions/runs/{run}/artifacts repository getArtifactsOfRun
- // ---
- // summary: Lists all artifacts for a repository run
- // produces:
- // - application/json
- // parameters:
- // - name: owner
- // in: path
- // description: owner of the repo
- // type: string
- // required: true
- // - name: repo
- // in: path
- // description: name of the repository
- // type: string
- // required: true
- // - name: run
- // in: path
- // description: runid of the workflow run
- // type: integer
- // required: true
- // - name: name
- // in: query
- // description: name of the artifact
- // type: string
- // required: false
- // responses:
- // "200":
- // "$ref": "#/responses/ArtifactsList"
- // "400":
- // "$ref": "#/responses/error"
- // "404":
- // "$ref": "#/responses/notFound"
-
- repoID := ctx.Repo.Repository.ID
- artifactName := ctx.Req.URL.Query().Get("name")
-
- runID := ctx.PathParamInt64("run")
-
- artifacts, total, err := db.FindAndCount[actions_model.ActionArtifact](ctx, actions_model.FindArtifactsOptions{
- RepoID: repoID,
- RunID: runID,
- ArtifactName: artifactName,
- FinalizedArtifactsV4: true,
- ListOptions: utils.GetListOptions(ctx),
- })
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
-
- res := new(api.ActionArtifactsResponse)
- res.TotalCount = total
-
- res.Entries = make([]*api.ActionArtifact, len(artifacts))
- for i := range artifacts {
- convertedArtifact, err := convert.ToActionArtifact(ctx.Repo.Repository, artifacts[i])
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
- res.Entries[i] = convertedArtifact
- }
-
- ctx.JSON(http.StatusOK, &res)
- }
-
- // DeleteActionRun Delete a workflow run
- func DeleteActionRun(ctx *context.APIContext) {
- // swagger:operation DELETE /repos/{owner}/{repo}/actions/runs/{run} repository deleteActionRun
- // ---
- // summary: Delete a workflow run
- // produces:
- // - application/json
- // parameters:
- // - name: owner
- // in: path
- // description: owner of the repo
- // type: string
- // required: true
- // - name: repo
- // in: path
- // description: name of the repository
- // type: string
- // required: true
- // - name: run
- // in: path
- // description: runid of the workflow run
- // type: integer
- // required: true
- // responses:
- // "204":
- // description: "No Content"
- // "400":
- // "$ref": "#/responses/error"
- // "404":
- // "$ref": "#/responses/notFound"
-
- runID := ctx.PathParamInt64("run")
- run, err := actions_model.GetRunByRepoAndID(ctx, ctx.Repo.Repository.ID, runID)
- if errors.Is(err, util.ErrNotExist) {
- ctx.APIErrorNotFound(err)
- return
- } else if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
- if !run.Status.IsDone() {
- ctx.APIError(http.StatusBadRequest, "this workflow run is not done")
- return
- }
-
- if err := actions_service.DeleteRun(ctx, run); err != nil {
- ctx.APIErrorInternal(err)
- return
- }
- ctx.Status(http.StatusNoContent)
- }
-
- // GetArtifacts Lists all artifacts for a repository.
- func GetArtifacts(ctx *context.APIContext) {
- // swagger:operation GET /repos/{owner}/{repo}/actions/artifacts repository getArtifacts
- // ---
- // summary: Lists all artifacts for a repository
- // produces:
- // - application/json
- // parameters:
- // - name: owner
- // in: path
- // description: owner of the repo
- // type: string
- // required: true
- // - name: repo
- // in: path
- // description: name of the repository
- // type: string
- // required: true
- // - name: name
- // in: query
- // description: name of the artifact
- // type: string
- // required: false
- // responses:
- // "200":
- // "$ref": "#/responses/ArtifactsList"
- // "400":
- // "$ref": "#/responses/error"
- // "404":
- // "$ref": "#/responses/notFound"
-
- repoID := ctx.Repo.Repository.ID
- artifactName := ctx.Req.URL.Query().Get("name")
-
- artifacts, total, err := db.FindAndCount[actions_model.ActionArtifact](ctx, actions_model.FindArtifactsOptions{
- RepoID: repoID,
- ArtifactName: artifactName,
- FinalizedArtifactsV4: true,
- ListOptions: utils.GetListOptions(ctx),
- })
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
-
- res := new(api.ActionArtifactsResponse)
- res.TotalCount = total
-
- res.Entries = make([]*api.ActionArtifact, len(artifacts))
- for i := range artifacts {
- convertedArtifact, err := convert.ToActionArtifact(ctx.Repo.Repository, artifacts[i])
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
- res.Entries[i] = convertedArtifact
- }
-
- ctx.JSON(http.StatusOK, &res)
- }
-
- // GetArtifact Gets a specific artifact for a workflow run.
- func GetArtifact(ctx *context.APIContext) {
- // swagger:operation GET /repos/{owner}/{repo}/actions/artifacts/{artifact_id} repository getArtifact
- // ---
- // summary: Gets a specific artifact for a workflow run
- // produces:
- // - application/json
- // parameters:
- // - name: owner
- // in: path
- // description: owner of the repo
- // type: string
- // required: true
- // - name: repo
- // in: path
- // description: name of the repository
- // type: string
- // required: true
- // - name: artifact_id
- // in: path
- // description: id of the artifact
- // type: string
- // required: true
- // responses:
- // "200":
- // "$ref": "#/responses/Artifact"
- // "400":
- // "$ref": "#/responses/error"
- // "404":
- // "$ref": "#/responses/notFound"
-
- art := getArtifactByPathParam(ctx, ctx.Repo.Repository)
- if ctx.Written() {
- return
- }
-
- if actions.IsArtifactV4(art) {
- convertedArtifact, err := convert.ToActionArtifact(ctx.Repo.Repository, art)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
- ctx.JSON(http.StatusOK, convertedArtifact)
- return
- }
- // v3 not supported due to not having one unique id
- ctx.APIError(http.StatusNotFound, "Artifact not found")
- }
-
- // DeleteArtifact Deletes a specific artifact for a workflow run.
- func DeleteArtifact(ctx *context.APIContext) {
- // swagger:operation DELETE /repos/{owner}/{repo}/actions/artifacts/{artifact_id} repository deleteArtifact
- // ---
- // summary: Deletes a specific artifact for a workflow run
- // produces:
- // - application/json
- // parameters:
- // - name: owner
- // in: path
- // description: owner of the repo
- // type: string
- // required: true
- // - name: repo
- // in: path
- // description: name of the repository
- // type: string
- // required: true
- // - name: artifact_id
- // in: path
- // description: id of the artifact
- // type: string
- // required: true
- // responses:
- // "204":
- // description: "No Content"
- // "400":
- // "$ref": "#/responses/error"
- // "404":
- // "$ref": "#/responses/notFound"
-
- art := getArtifactByPathParam(ctx, ctx.Repo.Repository)
- if ctx.Written() {
- return
- }
-
- if actions.IsArtifactV4(art) {
- if err := actions_model.SetArtifactNeedDelete(ctx, art.RunID, art.ArtifactName); err != nil {
- ctx.APIErrorInternal(err)
- return
- }
- ctx.Status(http.StatusNoContent)
- return
- }
- // v3 not supported due to not having one unique id
- ctx.APIError(http.StatusNotFound, "Artifact not found")
- }
-
- func buildSignature(endp string, expires, artifactID int64) []byte {
- mac := hmac.New(sha256.New, setting.GetGeneralTokenSigningSecret())
- mac.Write([]byte(endp))
- fmt.Fprint(mac, expires)
- fmt.Fprint(mac, artifactID)
- return mac.Sum(nil)
- }
-
- func buildDownloadRawEndpoint(repo *repo_model.Repository, artifactID int64) string {
- return fmt.Sprintf("api/v1/repos/%s/%s/actions/artifacts/%d/zip/raw", url.PathEscape(repo.OwnerName), url.PathEscape(repo.Name), artifactID)
- }
-
- func buildSigURL(ctx go_context.Context, endPoint string, artifactID int64) string {
- // endPoint is a path like "api/v1/repos/owner/repo/actions/artifacts/1/zip/raw"
- expires := time.Now().Add(60 * time.Minute).Unix()
- uploadURL := httplib.GuessCurrentAppURL(ctx) + endPoint + "?sig=" + base64.URLEncoding.EncodeToString(buildSignature(endPoint, expires, artifactID)) + "&expires=" + strconv.FormatInt(expires, 10)
- return uploadURL
- }
-
- // DownloadArtifact Downloads a specific artifact for a workflow run redirects to blob url.
- func DownloadArtifact(ctx *context.APIContext) {
- // swagger:operation GET /repos/{owner}/{repo}/actions/artifacts/{artifact_id}/zip repository downloadArtifact
- // ---
- // summary: Downloads a specific artifact for a workflow run redirects to blob url
- // produces:
- // - application/json
- // parameters:
- // - name: owner
- // in: path
- // description: owner of the repo
- // type: string
- // required: true
- // - name: repo
- // in: path
- // description: name of the repository
- // type: string
- // required: true
- // - name: artifact_id
- // in: path
- // description: id of the artifact
- // type: string
- // required: true
- // responses:
- // "302":
- // description: redirect to the blob download
- // "400":
- // "$ref": "#/responses/error"
- // "404":
- // "$ref": "#/responses/notFound"
-
- art := getArtifactByPathParam(ctx, ctx.Repo.Repository)
- if ctx.Written() {
- return
- }
-
- // if artifacts status is not uploaded-confirmed, treat it as not found
- if art.Status == actions_model.ArtifactStatusExpired {
- ctx.APIError(http.StatusNotFound, "Artifact has expired")
- return
- }
- ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.zip; filename*=UTF-8''%s.zip", url.PathEscape(art.ArtifactName), art.ArtifactName))
-
- if actions.IsArtifactV4(art) {
- ok, err := actions.DownloadArtifactV4ServeDirectOnly(ctx.Base, art)
- if ok {
- return
- }
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
-
- redirectURL := buildSigURL(ctx, buildDownloadRawEndpoint(ctx.Repo.Repository, art.ID), art.ID)
- ctx.Redirect(redirectURL, http.StatusFound)
- return
- }
- // v3 not supported due to not having one unique id
- ctx.APIError(http.StatusNotFound, "Artifact not found")
- }
-
- // DownloadArtifactRaw Downloads a specific artifact for a workflow run directly.
- func DownloadArtifactRaw(ctx *context.APIContext) {
- // it doesn't use repoAssignment middleware, so it needs to prepare the repo and check permission (sig) by itself
- repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, ctx.PathParam("username"), ctx.PathParam("reponame"))
- if err != nil {
- if errors.Is(err, util.ErrNotExist) {
- ctx.APIErrorNotFound()
- } else {
- ctx.APIErrorInternal(err)
- }
- return
- }
- art := getArtifactByPathParam(ctx, repo)
- if ctx.Written() {
- return
- }
-
- sigStr := ctx.Req.URL.Query().Get("sig")
- expiresStr := ctx.Req.URL.Query().Get("expires")
- sigBytes, _ := base64.URLEncoding.DecodeString(sigStr)
- expires, _ := strconv.ParseInt(expiresStr, 10, 64)
-
- expectedSig := buildSignature(buildDownloadRawEndpoint(repo, art.ID), expires, art.ID)
- if !hmac.Equal(sigBytes, expectedSig) {
- ctx.APIError(http.StatusUnauthorized, "Error unauthorized")
- return
- }
- t := time.Unix(expires, 0)
- if t.Before(time.Now()) {
- ctx.APIError(http.StatusUnauthorized, "Error link expired")
- return
- }
-
- // if artifacts status is not uploaded-confirmed, treat it as not found
- if art.Status == actions_model.ArtifactStatusExpired {
- ctx.APIError(http.StatusNotFound, "Artifact has expired")
- return
- }
- ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.zip; filename*=UTF-8''%s.zip", url.PathEscape(art.ArtifactName), art.ArtifactName))
-
- if actions.IsArtifactV4(art) {
- err := actions.DownloadArtifactV4(ctx.Base, art)
- if err != nil {
- ctx.APIErrorInternal(err)
- return
- }
- return
- }
- // v3 not supported due to not having one unique id
- ctx.APIError(http.StatusNotFound, "artifact not found")
- }
-
- // Try to get the artifact by ID and check access
- func getArtifactByPathParam(ctx *context.APIContext, repo *repo_model.Repository) *actions_model.ActionArtifact {
- artifactID := ctx.PathParamInt64("artifact_id")
-
- art, ok, err := db.GetByID[actions_model.ActionArtifact](ctx, artifactID)
- if err != nil {
- ctx.APIErrorInternal(err)
- return nil
- }
- // if artifacts status is not uploaded-confirmed, treat it as not found
- // only check RepoID here, because the repository owner may change over the time
- if !ok ||
- art.RepoID != repo.ID ||
- art.Status != actions_model.ArtifactStatusUploadConfirmed && art.Status != actions_model.ArtifactStatusExpired {
- ctx.APIError(http.StatusNotFound, "artifact not found")
- return nil
- }
- return art
- }
|