diff --git a/cypress/fixtures/build_pending_approval.json b/cypress/fixtures/build_pending_approval.json new file mode 100644 index 000000000..35415d40b --- /dev/null +++ b/cypress/fixtures/build_pending_approval.json @@ -0,0 +1,27 @@ +{ + "id": 15, + "repo_id": 1, + "number": 8, + "parent": 1, + "event": "pull_request", + "status": "pending approval", + "error": "", + "enqueued": 1572980376, + "created": 1572980376, + "started": 1572980375, + "finished": 1572980375, + "deploy": "", + "clone": "https://github.com/github/octocat.git", + "source": "https://github.com/github/octocat/commit/9b1d8bded6e992ab660eaee527c5e3232d0a2441", + "title": "push received from https://github.com/github/octocat", + "message": "fixing docker params", + "commit": "9b1d8bded6e992ab660eaee527c5e3232d0a2441", + "sender": "CookieCat", + "author": "CookieCat", + "branch": "infra", + "ref": "refs/heads/infra", + "base_ref": "", + "host": "", + "runtime": "docker", + "distribution": "linux" +} diff --git a/cypress/fixtures/repository.json b/cypress/fixtures/repository.json index 3f91be0f5..1f2e66fdf 100644 --- a/cypress/fixtures/repository.json +++ b/cypress/fixtures/repository.json @@ -19,5 +19,6 @@ "allow_deploy": false, "allow_tag": false, "allow_comment": false, - "pipeline_type": "yaml" + "pipeline_type": "yaml", + "approve_build": "fork-always" } diff --git a/cypress/fixtures/repository_updated.json b/cypress/fixtures/repository_updated.json index b9b18ffbf..8da3b8bbf 100644 --- a/cypress/fixtures/repository_updated.json +++ b/cypress/fixtures/repository_updated.json @@ -19,5 +19,6 @@ "allow_deploy": true, "allow_tag": true, "allow_comment": false, - "pipeline_type": "yaml" + "pipeline_type": "yaml", + "approve_build": "fork-no-write" } diff --git a/cypress/integration/build.spec.js b/cypress/integration/build.spec.js index b4b4cde3e..f296617c9 100644 --- a/cypress/integration/build.spec.js +++ b/cypress/integration/build.spec.js @@ -203,6 +203,33 @@ context('Build', () => { }); }); + context('server stubbed Approve Build', () => { + beforeEach(() => { + cy.visit('/github/octocat/8'); + cy.server(); + cy.fixture('build_pending_approval.json').as('approveBuild'); + cy.route({ + method: 'POST', + url: 'api/v1/repos/*/*/builds/*/approve', + status: 200, + response: 'approved build github/octocat/8', + }); + cy.get('[data-test=approve-build]').as('approvedBuild'); + }); + + it('there should be a notice banner', () => { + cy.get('[data-test=approve-build-notice').should('exist'); + }); + + it('clicking cancel build should show alert', () => { + cy.get('@approvedBuild').click(); + cy.get('[data-test=alert]').should( + 'contain', + 'approved build github/octocat/8', + ); + }); + }); + context('visit running build', () => { beforeEach(() => { cy.visit('/github/octocat/1'); diff --git a/cypress/integration/repo_settings.spec.js b/cypress/integration/repo_settings.spec.js index 8755b6c28..4a491470b 100644 --- a/cypress/integration/repo_settings.spec.js +++ b/cypress/integration/repo_settings.spec.js @@ -71,6 +71,15 @@ context('Repo Settings', () => { cy.get('@accessRadio').should('have.checked'); }); + it('clicking outside contributor approval policy should toggle', () => { + cy.get('[data-test=repo-radio-fork-no-write] input').as( + 'forkPolicyRadio', + ); + cy.get('@forkPolicyRadio').should('not.have.checked'); + cy.get('@forkPolicyRadio').click({ force: true }); + cy.get('@forkPolicyRadio').should('have.checked'); + }); + it('clicking pipeline type radio should toggle all values', () => { cy.get('[data-test=repo-radio-private] input').as('pipelineTypeRadio'); cy.get('@pipelineTypeRadio').should('not.have.checked'); diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 979bb0caf..ebf2326b3 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -63,6 +63,7 @@ Cypress.Commands.add('stubBuild', () => { cy.fixture('build_failure.json').as('failureBuild'); cy.fixture('build_error.json').as('errorBuild'); cy.fixture('build_canceled.json').as('cancelBuild'); + cy.fixture('build_pending_approval.json').as('pendingApprovalBuild'); cy.route({ method: 'GET', url: 'api/v1/repos/*/*/builds/1', @@ -105,6 +106,12 @@ Cypress.Commands.add('stubBuild', () => { status: 200, response: '@successBuild', }); + cy.route({ + method: 'GET', + url: 'api/v1/repos/*/*/builds/8', + status: 200, + response: `@pendingApprovalBuild`, + }); }); Cypress.Commands.add('stubBuilds', () => { diff --git a/docker-compose.yml b/docker-compose.yml index 2498e4d92..c7c386809 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -45,6 +45,7 @@ services: QUEUE_DRIVER: redis QUEUE_ADDR: 'redis://redis:6379' QUEUE_PRIVATE_KEY: 'tCIevHOBq6DdN5SSBtteXUusjjd0fOqzk2eyi0DMq04NewmShNKQeUbbp3vkvIckb4pCxc+vxUo+mYf/vzOaSg==' + QUEUE_PUBLIC_KEY: 'DXsJkoTSkHlG26d75LyHJG+KQsXPr8VKPpmH/78zmko=' SCM_DRIVER: github SCM_CONTEXT: 'continuous-integration/vela' SECRET_VAULT: 'true' @@ -55,7 +56,6 @@ services: VELA_LOG_LEVEL: trace # comment the line below to use registration flow VELA_SECRET: 'zB7mrKDTZqNeNTD8z47yG4DHywspAh' - QUEUE_PUBLIC_KEY: 'DXsJkoTSkHlG26d75LyHJG+KQsXPr8VKPpmH/78zmko=' VELA_SERVER_PRIVATE_KEY: 'F534FF2A080E45F38E05DC70752E6787' VELA_USER_REFRESH_TOKEN_DURATION: 90m VELA_USER_ACCESS_TOKEN_DURATION: 60m @@ -88,7 +88,6 @@ services: EXECUTOR_DRIVER: linux QUEUE_DRIVER: redis QUEUE_ADDR: 'redis://redis:6379' - QUEUE_PUBLIC_KEY: 'DXsJkoTSkHlG26d75LyHJG+KQsXPr8VKPpmH/78zmko=' VELA_BUILD_LIMIT: 1 VELA_BUILD_TIMEOUT: 30m VELA_LOG_LEVEL: trace diff --git a/src/elm/Api.elm b/src/elm/Api.elm index b7a28e54f..733537170 100644 --- a/src/elm/Api.elm +++ b/src/elm/Api.elm @@ -8,6 +8,7 @@ module Api exposing , addDeployment , addSchedule , addSecret + , approveBuild , cancelBuild , chownRepo , deleteRepo @@ -556,6 +557,14 @@ restartBuild model org repository buildNumber = |> withAuth model.session +{-| approveBuild : approves a build +-} +approveBuild : PartialModel a -> Org -> Repo -> BuildNumber -> Request String +approveBuild model org repository buildNumber = + post model.velaAPI (Endpoint.ApproveBuild org repository buildNumber) Http.emptyBody Json.Decode.string + |> withAuth model.session + + {-| cancelBuild : cancels a build -} cancelBuild : PartialModel a -> Org -> Repo -> BuildNumber -> Request Build diff --git a/src/elm/Api/Endpoint.elm b/src/elm/Api/Endpoint.elm index 647629f62..ba83f1006 100644 --- a/src/elm/Api/Endpoint.elm +++ b/src/elm/Api/Endpoint.elm @@ -55,6 +55,7 @@ type Endpoint | Builds (Maybe Pagination.Page) (Maybe Pagination.PerPage) (Maybe Event) Org Repo | Build Org Repo BuildNumber | CancelBuild Org Repo BuildNumber + | ApproveBuild Org Repo BuildNumber | Services (Maybe Pagination.Page) (Maybe Pagination.PerPage) Org Repo BuildNumber | ServiceLogs Org Repo BuildNumber ServiceNumber | Steps (Maybe Pagination.Page) (Maybe Pagination.PerPage) Org Repo BuildNumber @@ -124,6 +125,9 @@ toUrl api endpoint = CancelBuild org repo buildNumber -> url api [ "repos", org, repo, "builds", buildNumber, "cancel" ] [] + ApproveBuild org repo buildNumber -> + url api [ "repos", org, repo, "builds", buildNumber, "approve" ] [] + Services maybePage maybePerPage org repo buildNumber -> url api [ "repos", org, repo, "builds", buildNumber, "services" ] <| Pagination.toQueryParams maybePage maybePerPage diff --git a/src/elm/Help/Commands.elm b/src/elm/Help/Commands.elm index c89681a73..1d8f67c80 100644 --- a/src/elm/Help/Commands.elm +++ b/src/elm/Help/Commands.elm @@ -109,13 +109,13 @@ commands page = [ listDeployments org repo ] Pages.Build org repo buildNumber _ -> - [ viewBuild org repo buildNumber, restartBuild org repo buildNumber, cancelBuild org repo buildNumber, listSteps org repo buildNumber, viewStep org repo buildNumber ] + [ viewBuild org repo buildNumber, approveBuild org repo buildNumber, restartBuild org repo buildNumber, cancelBuild org repo buildNumber, listSteps org repo buildNumber, viewStep org repo buildNumber ] Pages.BuildServices org repo buildNumber _ -> - [ viewBuild org repo buildNumber, restartBuild org repo buildNumber, cancelBuild org repo buildNumber, listServices org repo buildNumber, viewService org repo buildNumber ] + [ viewBuild org repo buildNumber, approveBuild org repo buildNumber, restartBuild org repo buildNumber, cancelBuild org repo buildNumber, listServices org repo buildNumber, viewService org repo buildNumber ] Pages.BuildPipeline org repo buildNumber _ _ -> - [ viewBuild org repo buildNumber, restartBuild org repo buildNumber ] + [ viewBuild org repo buildNumber, approveBuild org repo buildNumber, restartBuild org repo buildNumber ] Pages.BuildGraph org repo buildNumber -> [ viewBuild org repo buildNumber, restartBuild org repo buildNumber ] @@ -255,6 +255,27 @@ viewBuild org repo buildNumber = Command name content docs noIssue +{-| approveBuild : returns cli command for approving a build + + eg. + vela approve build --org octocat --repo hello-world --build 1 + +-} +approveBuild : Org -> Repo -> BuildNumber -> Command +approveBuild org repo buildNumber = + let + name = + "Approve Build" + + content = + Just <| "vela approve build " ++ buildArgs org repo buildNumber + + docs = + Just "/build/approve" + in + Command name content docs noIssue + + {-| restartBuild : returns cli command for restarting a build eg. diff --git a/src/elm/Main.elm b/src/elm/Main.elm index 71d9fd234..5b06671b0 100644 --- a/src/elm/Main.elm +++ b/src/elm/Main.elm @@ -435,10 +435,12 @@ type Msg | RepairRepo Repository | UpdateRepoEvent Org Repo Field Bool | UpdateRepoAccess Org Repo Field String + | UpdateRepoForkPolicy Org Repo Field String | UpdateRepoPipelineType Org Repo Field String | UpdateRepoLimit Org Repo Field Int | UpdateRepoTimeout Org Repo Field Int | UpdateRepoCounter Org Repo Field Int + | ApproveBuild Org Repo BuildNumber | RestartBuild Org Repo BuildNumber | CancelBuild Org Repo BuildNumber | RedeliverHook Org Repo HookNumber @@ -457,6 +459,7 @@ type Msg | RepoUpdatedResponse Field (Result (Http.Detailed.Error String) ( Http.Metadata, Repository )) | RepoChownedResponse Repository (Result (Http.Detailed.Error String) ( Http.Metadata, String )) | RepoRepairedResponse Repository (Result (Http.Detailed.Error String) ( Http.Metadata, String )) + | ApprovedBuildResponse Org Repo BuildNumber (Result (Http.Detailed.Error String) ( Http.Metadata, String )) | RestartedBuildResponse Org Repo BuildNumber (Result (Http.Detailed.Error String) ( Http.Metadata, Build )) | CancelBuildResponse Org Repo BuildNumber (Result (Http.Detailed.Error String) ( Http.Metadata, Build )) | OrgBuildsResponse Org (Result (Http.Detailed.Error String) ( Http.Metadata, Builds )) @@ -1223,6 +1226,28 @@ update msg model = , cmd ) + UpdateRepoForkPolicy org repo field value -> + let + payload : UpdateRepositoryPayload + payload = + buildUpdateRepoStringPayload field value + + cmd = + if Pages.RepoSettings.validForkPolicyUpdate rm.repo payload then + let + body : Http.Body + body = + Http.jsonBody <| encodeUpdateRepository payload + in + Api.try (RepoUpdatedResponse field) (Api.updateRepository model org repo body) + + else + Cmd.none + in + ( model + , cmd + ) + UpdateRepoPipelineType org repo field value -> let payload : UpdateRepositoryPayload @@ -1287,6 +1312,15 @@ update msg model = , Api.try (RepoUpdatedResponse field) (Api.updateRepository model org repo body) ) + ApproveBuild org repo buildNumber -> + let + newModel = + { model | buildMenuOpen = [] } + in + ( newModel + , approveBuild model org repo buildNumber + ) + RestartBuild org repo buildNumber -> let newModel = @@ -1630,6 +1664,15 @@ update msg model = Err error -> ( model, addError error ) + ApprovedBuildResponse org repo buildNumber response -> + case response of + Ok _ -> + ( model, Cmd.none ) + |> Alerting.addToastIfUnique Alerts.successConfig AlertsUpdate (Alerts.Success "Success" ("Build approved to run " ++ String.join "/" [ org, repo, buildNumber ]) Nothing) + + Err error -> + ( model, addError error ) + RestartedBuildResponse org repo buildNumber response -> case response of Ok ( _, build ) -> @@ -4819,7 +4862,7 @@ homeMsgs = -} navMsgs : Nav.Msgs Msg navMsgs = - Nav.Msgs FetchSourceRepositories ToggleFavorite RefreshSettings RefreshHooks RefreshSecrets RestartBuild CancelBuild + Nav.Msgs FetchSourceRepositories ToggleFavorite RefreshSettings RefreshHooks RefreshSecrets ApproveBuild RestartBuild CancelBuild {-| sourceReposMsgs : prepares the input record required for the SourceRepos page to route Msgs back to Main.elm @@ -4833,7 +4876,7 @@ sourceReposMsgs = -} repoSettingsMsgs : Pages.RepoSettings.Msgs Msg repoSettingsMsgs = - Pages.RepoSettings.Msgs UpdateRepoEvent UpdateRepoAccess UpdateRepoLimit ChangeRepoLimit UpdateRepoTimeout ChangeRepoTimeout UpdateRepoCounter ChangeRepoCounter DisableRepo EnableRepo Copy ChownRepo RepairRepo UpdateRepoPipelineType + Pages.RepoSettings.Msgs UpdateRepoEvent UpdateRepoAccess UpdateRepoForkPolicy UpdateRepoLimit ChangeRepoLimit UpdateRepoTimeout ChangeRepoTimeout UpdateRepoCounter ChangeRepoCounter DisableRepo EnableRepo Copy ChownRepo RepairRepo UpdateRepoPipelineType {-| buildMsgs : prepares the input record required for the Build pages to route Msgs back to Main.elm @@ -4846,6 +4889,7 @@ buildMsgs = , collapseAllServices = CollapseAllServices , expandAllServices = ExpandAllServices , expandService = ExpandService + , approveBuild = ApproveBuild , restartBuild = RestartBuild , cancelBuild = CancelBuild , toggle = ShowHideBuildMenu @@ -5027,6 +5071,11 @@ getBuildServicesLogs model org repo buildNumber services logFocus refresh = services +approveBuild : Model -> Org -> Repo -> BuildNumber -> Cmd Msg +approveBuild model org repo buildNumber = + Api.try (ApprovedBuildResponse org repo buildNumber) <| Api.approveBuild model org repo buildNumber + + restartBuild : Model -> Org -> Repo -> BuildNumber -> Cmd Msg restartBuild model org repo buildNumber = Api.try (RestartedBuildResponse org repo buildNumber) <| Api.restartBuild model org repo buildNumber diff --git a/src/elm/Nav.elm b/src/elm/Nav.elm index 2c776b2f6..049591671 100644 --- a/src/elm/Nav.elm +++ b/src/elm/Nav.elm @@ -65,6 +65,7 @@ type alias Msgs msg = , refreshSettings : Org -> Repo -> msg , refreshHooks : Org -> Repo -> msg , refreshSecrets : Engine -> SecretType -> Org -> Repo -> msg + , approveBuild : Org -> Repo -> BuildNumber -> msg , restartBuild : Org -> Repo -> BuildNumber -> msg , cancelBuild : Org -> Repo -> BuildNumber -> msg } @@ -94,7 +95,7 @@ viewNav model msgs = {-| navButtons : uses current page to build the commonly used button on the right side of the nav -} navButtons : PartialModel a -> Msgs msg -> Html msg -navButtons model { fetchSourceRepos, toggleFavorite, restartBuild, cancelBuild } = +navButtons model { fetchSourceRepos, toggleFavorite, approveBuild, restartBuild, cancelBuild } = case model.page of Pages.Overview -> a @@ -149,19 +150,22 @@ navButtons model { fetchSourceRepos, toggleFavorite, restartBuild, cancelBuild } Pages.Build org repo _ _ -> div [ class "buttons" ] - [ cancelBuildButton org repo model.repo.build.build cancelBuild + [ approveBuildButton org repo model.repo.build.build approveBuild + , cancelBuildButton org repo model.repo.build.build cancelBuild , restartBuildButton org repo model.repo.build.build restartBuild ] Pages.BuildServices org repo _ _ -> div [ class "buttons" ] - [ cancelBuildButton org repo model.repo.build.build cancelBuild + [ approveBuildButton org repo model.repo.build.build approveBuild + , cancelBuildButton org repo model.repo.build.build cancelBuild , restartBuildButton org repo model.repo.build.build restartBuild ] Pages.BuildPipeline org repo _ _ _ -> div [ class "buttons" ] - [ cancelBuildButton org repo model.repo.build.build cancelBuild + [ approveBuildButton org repo model.repo.build.build approveBuild + , cancelBuildButton org repo model.repo.build.build cancelBuild , restartBuildButton org repo model.repo.build.build restartBuild ] @@ -511,6 +515,9 @@ cancelBuildButton org repo build cancelBuild = Vela.Pending -> cancelButton + Vela.PendingApproval -> + cancelButton + _ -> text "" @@ -524,16 +531,55 @@ restartBuildButton : Org -> Repo -> WebData Build -> (Org -> Repo -> BuildNumber restartBuildButton org repo build restartBuild = case build of RemoteData.Success b -> - button - [ classList - [ ( "button", True ) - , ( "-outline", True ) - ] - , onClick <| restartBuild org repo <| String.fromInt b.number - , Util.testAttribute "restart-build" - ] - [ text "Restart Build" - ] + let + restartButton = + button + [ classList + [ ( "button", True ) + , ( "-outline", True ) + ] + , onClick <| restartBuild org repo <| String.fromInt b.number + , Util.testAttribute "restart-build" + ] + [ text "Restart Build" + ] + in + case b.status of + Vela.PendingApproval -> + text "" + + _ -> + restartButton + + _ -> + text "" + + +{-| approveBuildButton: takes org repo and build number and renders button to approve a build run +-} +approveBuildButton : Org -> Repo -> WebData Build -> (Org -> Repo -> BuildNumber -> msg) -> Html msg +approveBuildButton org repo build approveBuild = + case build of + RemoteData.Success b -> + let + approveButton = + button + [ classList + [ ( "button", True ) + , ( "-outline", True ) + ] + , onClick <| approveBuild org repo <| String.fromInt b.number + , Util.testAttribute "approve-build" + ] + [ text "Approve Build" + ] + in + case b.status of + Vela.PendingApproval -> + approveButton + + _ -> + text "" _ -> text "" diff --git a/src/elm/Pages/Build/Model.elm b/src/elm/Pages/Build/Model.elm index f45189168..7ef108063 100644 --- a/src/elm/Pages/Build/Model.elm +++ b/src/elm/Pages/Build/Model.elm @@ -58,6 +58,7 @@ type alias Msgs msg = , expandAllServices : ExpandAll msg , expandService : Expand msg , logsMsgs : LogsMsgs msg + , approveBuild : ApproveBuild msg , restartBuild : RestartBuild msg , cancelBuild : CancelBuild msg , toggle : Maybe Int -> Maybe Bool -> msg @@ -74,6 +75,10 @@ type alias LogsMsgs msg = } +type alias ApproveBuild msg = + Org -> Repo -> BuildNumber -> msg + + type alias BuildGraphMsgs msg = { refresh : Org -> Repo -> BuildNumber -> msg , rotate : msg diff --git a/src/elm/Pages/Build/View.elm b/src/elm/Pages/Build/View.elm index ee1d388c5..231598a04 100644 --- a/src/elm/Pages/Build/View.elm +++ b/src/elm/Pages/Build/View.elm @@ -25,7 +25,7 @@ import Focus , resourceAndLineToFocusId , resourceToFocusId ) -import Html exposing (Html, a, button, code, details, div, li, small, span, strong, summary, table, td, text, tr, ul) +import Html exposing (Html, a, button, code, details, div, li, p, small, span, strong, summary, table, td, text, tr, ul) import Html.Attributes exposing ( attribute @@ -78,7 +78,7 @@ import Vela , Repo , RepoModel , Service - , Status + , Status(..) , Step , Steps , defaultStep @@ -127,10 +127,19 @@ wrapWithBuildPreview model msgs org repo buildNumber content = markdown = case build.build of RemoteData.Success bld -> - [ viewPreview msgs model.buildMenuOpen False model.time model.zone org repo rm.builds.showTimestamp bld - , viewBuildTabs model org repo buildNumber model.page - , content - ] + case bld.status of + PendingApproval -> + [ viewPreview msgs model.buildMenuOpen False model.time model.zone org repo rm.builds.showTimestamp bld + , p [ class "notice", Util.testAttribute "approve-build-notice" ] [ text "An admin of this repository must approve the build to run" ] + , viewBuildTabs model org repo buildNumber model.page + , content + ] + + _ -> + [ viewPreview msgs model.buildMenuOpen False model.time model.zone org repo rm.builds.showTimestamp bld + , viewBuildTabs model org repo buildNumber model.page + , content + ] RemoteData.Loading -> [ Util.largeLoader ] @@ -162,18 +171,41 @@ viewPreview msgs openMenu showMenu now zone org repo showTimestamp build = buildMenuAttributeList = Util.open (List.member build.id openMenu) ++ [ id "build-actions" ] + approveBuild : Html msgs + approveBuild = + case build.status of + Vela.PendingApproval -> + li [ class "build-menu-item" ] + [ a + [ href "#" + , class "menu-item" + , Util.onClickPreventDefault <| msgs.approveBuild org repo <| String.fromInt build.number + , Util.testAttribute "approve-build" + ] + [ text "Approve Build" + ] + ] + + _ -> + text "" + restartBuild : Html msgs restartBuild = - li [ class "build-menu-item" ] - [ a - [ href "#" - , class "menu-item" - , Util.onClickPreventDefault <| msgs.restartBuild org repo <| String.fromInt build.number - , Util.testAttribute "restart-build" - ] - [ text "Restart Build" - ] - ] + case build.status of + Vela.PendingApproval -> + text "" + + _ -> + li [ class "build-menu-item" ] + [ a + [ href "#" + , class "menu-item" + , Util.onClickPreventDefault <| msgs.restartBuild org repo <| String.fromInt build.number + , Util.testAttribute "restart-build" + ] + [ text "Restart Build" + ] + ] cancelBuild : Html msgs cancelBuild = @@ -190,6 +222,30 @@ viewPreview msgs openMenu showMenu now zone org repo showTimestamp build = ] ] + Vela.Pending -> + li [ class "build-menu-item" ] + [ a + [ href "#" + , class "menu-item" + , Util.onClickPreventDefault <| msgs.cancelBuild org repo <| String.fromInt build.number + , Util.testAttribute "cancel-build" + ] + [ text "Cancel Build" + ] + ] + + Vela.PendingApproval -> + li [ class "build-menu-item" ] + [ a + [ href "#" + , class "menu-item" + , Util.onClickPreventDefault <| msgs.cancelBuild org repo <| String.fromInt build.number + , Util.testAttribute "cancel-build" + ] + [ text "Cancel Build" + ] + ] + _ -> text "" @@ -201,7 +257,8 @@ viewPreview msgs openMenu showMenu now zone org repo showTimestamp build = , FeatherIcons.chevronDown |> FeatherIcons.withSize 20 |> FeatherIcons.withClass "details-icon-expand" |> FeatherIcons.toHtml [ attribute "aria-label" "show build actions" ] ] , ul [ class "build-menu", attribute "aria-hidden" "true", attribute "role" "menu" ] - [ restartBuild + [ approveBuild + , restartBuild , cancelBuild ] ] @@ -1196,6 +1253,9 @@ statusToClass status = Vela.Pending -> class "-pending" + Vela.PendingApproval -> + class "-pending" + Vela.Running -> class "-running" diff --git a/src/elm/Pages/RepoSettings.elm b/src/elm/Pages/RepoSettings.elm index d84ba829a..c9a7acd77 100644 --- a/src/elm/Pages/RepoSettings.elm +++ b/src/elm/Pages/RepoSettings.elm @@ -10,6 +10,7 @@ module Pages.RepoSettings exposing , enableUpdate , validAccessUpdate , validEventsUpdate + , validForkPolicyUpdate , validPipelineTypeUpdate , view ) @@ -108,6 +109,7 @@ type alias StringInputChange msg = type alias Msgs msg = { eventsUpdate : CheckboxUpdate msg , accessUpdate : RadioUpdate msg + , forkPolicyUpdate : RadioUpdate msg , limitUpdate : NumberInputChange msg , inLimitChange : StringInputChange msg , timeoutUpdate : NumberInputChange msg @@ -132,8 +134,8 @@ type alias Msgs msg = view : WebData Repository -> Msgs msg -> String -> String -> Int -> Html msg view repo actions velaAPI velaURL maxLimit = let - ( accessUpdate, pipelineTypeUpdate ) = - ( actions.accessUpdate, actions.pipelineTypeUpdate ) + ( accessUpdate, forkPolicyUpdate, pipelineTypeUpdate ) = + ( actions.accessUpdate, actions.forkPolicyUpdate, actions.pipelineTypeUpdate ) ( limitUpdate, inLimitChange ) = ( actions.limitUpdate, actions.inLimitChange ) @@ -155,6 +157,7 @@ view repo actions velaAPI velaURL maxLimit = div [ class "repo-settings", Util.testAttribute "repo-settings" ] [ events repo_ eventsUpdate , access repo_ accessUpdate + , forkPolicy repo_ forkPolicyUpdate , limit maxLimit repo_.inLimit repo_ limitUpdate inLimitChange , timeout repo_.inTimeout repo_ timeoutUpdate inTimeoutChange , counter repo_.inCounter repo_ counterUpdate inCounterChange @@ -191,6 +194,21 @@ access repo msg = ] +{-| forkPolicy : takes model and repo and renders the settings category for updating repo fork policy +-} +forkPolicy : Repository -> RadioUpdate msg -> Html msg +forkPolicy repo msg = + section [ class "settings", Util.testAttribute "repo-settings-fork-policy" ] + [ h2 [ class "settings-title" ] [ text "Outside Contributor Permissions" ] + , p [ class "settings-description" ] [ text "Change which pull request builds from forks need approval to run a build." ] + , div [ class "form-controls", class "-stack" ] + [ radio repo.approve_build "fork-always" "Always Require Admin Approval" <| msg repo.org repo.name "approve_build" "fork-always" + , radio repo.approve_build "fork-no-write" "Require Admin Approval When Contributor Is Read Only" <| msg repo.org repo.name "approve_build" "fork-no-write" + , radio repo.approve_build "never" "Never Require Admin Approval" <| msg repo.org repo.name "approve_build" "never" + ] + ] + + {-| pipelineType : takes model and repo and renders the settings category for updating repo pipeline type -} pipelineType : Repository -> RadioUpdate msg -> Html msg @@ -314,9 +332,6 @@ events repo msg = <| msg repo.org repo.name "allow_deploy" ] - , div [] - [ pullRequestEventWarning - ] ] @@ -393,7 +408,7 @@ radio value field title msg = , onClick msg ] [] - , label [ class "form-label", for <| "radio-" ++ field ] [ strong [] [ text title ], updateAccessTip field ] + , label [ class "form-label", for <| "radio-" ++ field ] [ strong [] [ text title ], updateAccessTip field, updateForkPolicyTip field ] ] @@ -451,21 +466,6 @@ limitWarning maxLimit inLimit = text "" -{-| pullRequestEventWarning : renders disclaimer for pull request exposure --} -pullRequestEventWarning : Html msg -pullRequestEventWarning = - p [ class "notice" ] - [ text "Disclaimer: Vela repos do NOT have the " - , strong [] [ text "pull_request" ] - , text " event enabled by default. For all public repositories, " - , strong [] [ text "any user" ] - , text ", even outside of the organization, can open a pull request, triggering a build. " - , strong [] [ text "The risks from this can include: changes to the pipeline, arbitrary code execution inside your environment, and exposure of Vela-accessible secrets in your repository." ] - , text " You can override this behavior, at your own risk." - ] - - {-| timeoutInput : takes repo, user input, and button action and renders the text input for updating build timeout. -} timeoutInput : Repository -> Maybe Int -> (String -> msg) -> Html msg @@ -796,6 +796,23 @@ validAccessUpdate originalRepo repoUpdate = False +{-| validForkPolicyUpdate : takes model webdata repo and repo fork policy update and determines if an update is necessary +-} +validForkPolicyUpdate : WebData Repository -> UpdateRepositoryPayload -> Bool +validForkPolicyUpdate originalRepo repoUpdate = + case originalRepo of + RemoteData.Success repo -> + case repoUpdate.approve_build of + Just approve_build -> + repo.approve_build /= approve_build + + Nothing -> + False + + _ -> + False + + {-| validPipelineTypeUpdate : takes model webdata repo and repo pipeline type update and determines if an update is necessary -} validPipelineTypeUpdate : WebData Repository -> UpdateRepositoryPayload -> Bool @@ -844,6 +861,24 @@ updateAccessTip field = text "" +{-| updateForkPolicyTip : takes field and returns the tip to display after the label. +-} +updateForkPolicyTip : Field -> Html msg +updateForkPolicyTip field = + case field of + "fork-always" -> + text " (repository admin must approve all builds from outside contributors)" + + "fork-no-write" -> + text " (repository admin must approve all builds from outside contributors with read-only access to the repo)" + + "never" -> + text " (any outside contributor can run a PR build)" + + _ -> + text "" + + {-| msgPrefix : takes update field and returns alert prefix. -} msgPrefix : Field -> String @@ -858,6 +893,9 @@ msgPrefix field = "visibility" -> "$ visibility set to " + "approve_build" -> + "$ approve build policy set to " + "allow_pull" -> "Pull events for $ " @@ -903,6 +941,9 @@ msgSuffix field repo = "visibility" -> repo.visibility ++ "." + "approve_build" -> + repo.approve_build ++ "." + "allow_pull" -> toggleText "allow_pull" repo.allow_pull diff --git a/src/elm/SvgBuilder.elm b/src/elm/SvgBuilder.elm index 0c5b35dec..ac133479e 100644 --- a/src/elm/SvgBuilder.elm +++ b/src/elm/SvgBuilder.elm @@ -588,6 +588,9 @@ buildStatusToIcon status = Vela.Pending -> buildPending + Vela.PendingApproval -> + buildPending + Vela.Running -> buildRunning @@ -615,6 +618,9 @@ recentBuildStatusToIcon status index = Vela.Pending -> buildHistoryPending index + Vela.PendingApproval -> + buildHistoryPending index + Vela.Running -> buildHistoryRunning index @@ -642,6 +648,9 @@ stepStatusToIcon status = Vela.Pending -> stepPending + Vela.PendingApproval -> + stepPending + Vela.Running -> stepRunning diff --git a/src/elm/Vela.elm b/src/elm/Vela.elm index eba416a62..86d3a3e10 100644 --- a/src/elm/Vela.elm +++ b/src/elm/Vela.elm @@ -930,6 +930,7 @@ type alias Repository = , timeout : Int , counter : Int , visibility : String + , approve_build : String , private : Bool , trusted : Bool , active : Bool @@ -980,6 +981,7 @@ decodeRepository = |> optional "timeout" int 0 |> optional "counter" int 0 |> optional "visibility" string "" + |> optional "approve_build" string "" |> optional "private" bool False |> optional "trusted" bool False |> optional "active" bool False @@ -1105,6 +1107,7 @@ type alias UpdateRepositoryPayload = , allow_tag : Maybe Bool , allow_comment : Maybe Bool , visibility : Maybe String + , approve_build : Maybe String , limit : Maybe Int , timeout : Maybe Int , counter : Maybe Int @@ -1118,7 +1121,7 @@ type alias Field = defaultUpdateRepositoryPayload : UpdateRepositoryPayload defaultUpdateRepositoryPayload = - UpdateRepositoryPayload Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing + UpdateRepositoryPayload Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing encodeUpdateRepository : UpdateRepositoryPayload -> Encode.Value @@ -1133,6 +1136,7 @@ encodeUpdateRepository repo = , ( "allow_tag", encodeOptional Encode.bool repo.allow_tag ) , ( "allow_comment", encodeOptional Encode.bool repo.allow_comment ) , ( "visibility", encodeOptional Encode.string repo.visibility ) + , ( "approve_build", encodeOptional Encode.string repo.approve_build ) , ( "build_limit", encodeOptional Encode.int repo.limit ) , ( "timeout", encodeOptional Encode.int repo.timeout ) , ( "counter", encodeOptional Encode.int repo.counter ) @@ -1200,6 +1204,9 @@ buildUpdateRepoStringPayload field value = "pipeline_type" -> { defaultUpdateRepositoryPayload | pipeline_type = Just value } + "approve_build" -> + { defaultUpdateRepositoryPayload | approve_build = Just value } + _ -> defaultUpdateRepositoryPayload @@ -1343,6 +1350,8 @@ type alias Build = , host : String , runtime : String , distribution : String + , approved_at : Int + , approved_by : String , deploy_payload : Maybe (List KeyValuePair) } @@ -1376,6 +1385,8 @@ decodeBuild = |> optional "host" string "" |> optional "runtime" string "" |> optional "distribution" string "" + |> optional "approved_at" int -1 + |> optional "approved_by" string "" |> optional "deploy_payload" decodeDeploymentParameters Nothing @@ -1544,6 +1555,7 @@ type Status | Killed | Canceled | Error + | PendingApproval {-| toStatus : helper to decode string to Status @@ -1554,6 +1566,9 @@ toStatus status = "pending" -> succeed Pending + "pending approval" -> + succeed PendingApproval + "running" -> succeed Running @@ -1614,6 +1629,9 @@ statusToString status = Pending -> "pending" + PendingApproval -> + "pending approval" + Running -> "running" @@ -1641,6 +1659,9 @@ isComplete status = Pending -> False + PendingApproval -> + False + Running -> False @@ -1679,6 +1700,9 @@ statusToFavicon status = Pending -> "-pending" + PendingApproval -> + "-pending" + Running -> "-running"