diff --git a/.env.example b/.env.example index 4f7781ca..a7740806 100644 --- a/.env.example +++ b/.env.example @@ -71,4 +71,5 @@ GROUP_HOSTNAMES=whoami.im.c.127.0.0.1.nip.io ADMIN_USER_EMAIL=andreas@dhis2.org ADMIN_USER_PASSWORD=somepassword -S3_BUCKET=test-db-manager-bucket +E2E_TEST_USER_EMAIL=im-e2e-test@dhis2.org +E2E_TEST_USER_PASSWORD=somepassword diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 54789aa7..58b38b85 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -3,39 +3,61 @@ name: e2e tests on: push: branches: [ master ] + pull_request: + branches: [ master ] workflow_dispatch: +env: + DEPLOY_ENVIRONMENT: ${{ (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy')) }} + jobs: e2e: + if: ${{ github.event_name == 'push' || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy')) }} runs-on: ubuntu-20.04 - timeout-minutes: 20 + timeout-minutes: 30 steps: - - uses: actions/checkout@v4 + - name: Wait for deploy + if: ${{ env.DEPLOY_ENVIRONMENT == 'true' }} + # Using fork of the upstream https://github.com/lewagon/wait-on-check-action, + # see https://github.com/lewagon/wait-on-check-action/issues/85 for more info. + uses: t3chguy/wait-on-check-action@master + with: + ref: ${{ github.head_ref }} + check-name: 'call-workflow / build' + repo-token: ${{ secrets.GITHUB_TOKEN }} + allowed-conclusions: success - - name: Install prerequisites - run: sudo apt install httpie - - - name: Pin kubectl to version v1.23.6 (https://github.com/aws/aws-cli/issues/6920) + - name: Construct tests target URL run: | - curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.23.6/bin/linux/amd64/kubectl - chmod +x ./kubectl - sudo mv ./kubectl /usr/local/bin/kubectl + BASE_HOSTNAME="api.im.dhis2.org" - - name: Configure kubectl - run: | - mkdir $PWD/.kube - echo "${{ secrets.KUBECONFIG }}" > $PWD/.kube/config - echo "KUBECONFIG=$PWD/.kube/config" >> $GITHUB_ENV + if [[ "$DEPLOY_ENVIRONMENT" == 'true' ]]; then + # convert to lower case + HEAD_BRANCH_LOWERCASE=${GITHUB_HEAD_REF,,} - - name: Run e2e - run: cd scripts/instances && ./stack_tests.sh - env: - HTTP: http --check-status - IM_HOST: https://dev.api.im.dhis2.org - USER_EMAIL: ${{ secrets.CI_TEST_USER_USER_EMAIL }} - PASSWORD: ${{ secrets.CI_TEST_USER_PASSWORD }} - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + # substitute all non-alphanumeric chars with hyphens "-" + HEAD_BRANCH_LOWERCASE_NO_NONALPHANUMERIC=${HEAD_BRANCH_LOWERCASE//[^[:alnum:]]/-} + + # trim to 25 chars, in order to fit into the 53 char Helm release name limit + # the rest of the chars up to the limit are saved for the static part of the release names + SANITIZED_HEAD_BRANCH=${HEAD_BRANCH_LOWERCASE_NO_NONALPHANUMERIC::25} + + echo "TARGET_URL=https://$SANITIZED_HEAD_BRANCH.$BASE_HOSTNAME" >> $GITHUB_ENV + else + echo "TARGET_URL=https://dev.$BASE_HOSTNAME" >> $GITHUB_ENV + fi + + # Don't set environment variable "API_URL" in this workflow with the action below. + # See https://github.com/convictional/trigger-workflow-and-wait/issues/62#issuecomment-1843267732 + - name: Trigger E2E Playwright tests + uses: convictional/trigger-workflow-and-wait@v1.6.5 + with: + owner: dhis2-sre + repo: im-web-client + github_token: ${{ secrets.DHIS2_SRE_BOT_GITHUB_TOKEN }} + workflow_file_name: playwright.yml + ref: DEVOPS-291 + client_payload: '{"api_url": "${{ env.TARGET_URL }}"}' send-slack-message: runs-on: ubuntu-latest diff --git a/cmd/serve/main.go b/cmd/serve/main.go index 2b1b1b9b..a0190e47 100644 --- a/cmd/serve/main.go +++ b/cmd/serve/main.go @@ -157,7 +157,7 @@ func run() error { integrationHandler := integration.NewHandler(dockerHubClient, cfg.InstanceService.Host, cfg.DatabaseManagerService.Host) - err = user.CreateAdminUser(cfg.AdminUser.Email, cfg.AdminUser.Password, userService, groupService) + err = user.CreateUser(cfg.AdminUser.Email, cfg.AdminUser.Password, userService, groupService, model.AdministratorGroupName, "admin") if err != nil { return err } @@ -165,6 +165,10 @@ func run() error { if err != nil { return err } + err = user.CreateUser(cfg.E2eTestUser.Email, cfg.E2eTestUser.Password, userService, groupService, model.DefaultGroupName, "e2e test") + if err != nil { + return err + } r := server.GetEngine(cfg.BasePath) diff --git a/helm/chart/templates/secret.yaml b/helm/chart/templates/secret.yaml index 39f3756e..efeeaa77 100644 --- a/helm/chart/templates/secret.yaml +++ b/helm/chart/templates/secret.yaml @@ -19,6 +19,9 @@ data: ADMIN_USER_EMAIL: {{ .Values.adminUser.email | b64enc }} ADMIN_USER_PASSWORD: {{ .Values.adminUser.password | b64enc }} + E2E_TEST_USER_EMAIL: {{ .Values.e2eTestUser.email | b64enc }} + E2E_TEST_USER_PASSWORD: {{ .Values.e2eTestUser.password | b64enc }} + PRIVATE_KEY: {{ .Values.privateKey | b64enc }} PUBLIC_KEY: {{ .Values.publicKey | b64enc }} {{- end }} diff --git a/helm/data/secrets/dev/values.yaml b/helm/data/secrets/dev/values.yaml index 191df9df..1d1a9e26 100644 --- a/helm/data/secrets/dev/values.yaml +++ b/helm/data/secrets/dev/values.yaml @@ -18,6 +18,9 @@ secrets: adminUser: email: ENC[AES256_GCM,data:8z9yVdnvtM+bHax+n263mz5K,iv:KeeoPhWFmMCGosoW5rXbLM3rgYIRD1S47BH/JusM6Qc=,tag:PdDcc0ypQ00p/tBVFjtutg==,type:str] password: ENC[AES256_GCM,data:JGvmU6XgRilMYnm4LobXPUqrHjC2v1K+0eMcRc4nE/I=,iv:+overL7GAUD9CcAgeQu13aBlwSCqXDkaQaegNXzFS3k=,tag:lVXJpJFl+5xZoKYetg4QpA==,type:str] +e2eTestUser: + email: ENC[AES256_GCM,data:7oYnuDQMDwdl46UjqfU2KDe7IYKT,iv:lSwGGwHyMIFKxqiTaJwQ5Nrj/kaKISv3+I35NTrDMcM=,tag:ZIagn0KC0Yz96MLI26Xl2Q==,type:str] + password: ENC[AES256_GCM,data:ostWcAh6SjmkFg5IOq/+9C4YhS9TZiFsbuDB3f6UTuHtf4b5,iv:LG4DxMMy8vF87nDky3Ayn8a6wgEPGY9dCgB5prcsKpU=,tag:myxH2e0Aztqs3AjYTT/aLQ==,type:str] database: username: ENC[AES256_GCM,data:iKom4rh9YA==,iv:KLiHM8qBzvgbTILhlFJ9wWO0set3mtTgFUQVg5uj7qE=,tag:qh8x2VKQODDqe07EM2MahQ==,type:str] password: ENC[AES256_GCM,data:R/jCbig/vA==,iv:ddwuxasUtlVeRavD95LAqtuA6JZ8jvnI4wTzm4S+ekE=,tag:FgmMQl/OuBgrihhGXGx6PQ==,type:str] @@ -35,8 +38,8 @@ sops: azure_kv: [] hc_vault: [] age: [] - lastmodified: "2023-08-17T05:06:40Z" - mac: ENC[AES256_GCM,data:r7/oh7OtHz2CONWpm8SvGqur4Rzve6OuTOzbwXAaxHH+quRsAX3ubboAxHOmaW/w1K0WWWQcUhGuWkmewhJvbLc0LQXogKePbg2GqhybD4yTpP3QqxQGzZUuXfNo6OsIlP+aamljkXSY6SG+vhrkZstoUu4i8uZmSvoFoK3s3Uo=,iv:LexKfTFAXUktXeonvi3sIWY6WRX3hs8DY4ttX+oFO8Q=,tag:9xE0L1KHaAbgD5lem4OJJQ==,type:str] + lastmodified: "2023-12-20T12:42:42Z" + mac: ENC[AES256_GCM,data:RggE/PKKTU+STWQvNTU03GhbvqTXAuxQI3XHivw6kNlgmJ/O6Qr8/HYXtCyF43emImLIrj7OS9FI+MxDxR9g/Mxh5qDTCeCKdA3/3D8ANBaa8/2lNZvp9Gbz0E8Zot+IilHUaBIJyXT8u0SfDUHVy7Vw9VGGZJs1EVYYSccUScw=,iv:Q+cwfv73GuRKcC5i6QPEIdtA2y8qyzPbvOKGY6PGYDw=,tag:jS0rmI0kIuPZdPCHy1ezvA==,type:str] pgp: [] unencrypted_suffix: _unencrypted version: 3.7.3 diff --git a/helm/data/secrets/prod/values.yaml b/helm/data/secrets/prod/values.yaml index 5d2eb3d3..d62f7fd8 100644 --- a/helm/data/secrets/prod/values.yaml +++ b/helm/data/secrets/prod/values.yaml @@ -18,6 +18,9 @@ secrets: adminUser: email: ENC[AES256_GCM,data:cSZlfydBrNhHL9oG9AkP/xnk,iv:HDV3jlxArtsNVdhZLrjwL8dYBKYQfQRoxIhhsibc2vs=,tag:E/6UU2+BGS4fLZ52AuxX3w==,type:str] password: ENC[AES256_GCM,data:yhakDKkcqHKNpLK+HCOfR+lz61K1IxRxSCbYVNSnbZY=,iv:zVadRLllf2QBJsjvs/UcWxXI9NkhMs3iaqG87+h68I8=,tag:/Zji0dVoW0M020GEUsWIQg==,type:str] +e2eTestUser: + email: ENC[AES256_GCM,data:RvH8F+WXmefOrIoE0B1GxNZ+d31Z,iv:Q0pmPvgCrD0Q6o9mMnCfOkH/Bpx8/tMqvTQ34Yaz/00=,tag:4cuoTQeRPEeCTpXIiYz2HQ==,type:str] + password: ENC[AES256_GCM,data:ytCwhkInRkiM8ucyYWN73Svam0hqwPiq0FgENQAwcIUiuLvk,iv:f3y3AP+DXOTjafTO9ajxCZtpgnQbm2nuQ05ACHjQs/g=,tag:Dwuj/ddTgiXGxDwCfEgedg==,type:str] database: username: ENC[AES256_GCM,data:thhg7NHEsQ==,iv:pG57pEEyVgosnK2TZjIEvJPEyAk0IgkEk5lD7pwSNlQ=,tag:cvVTZUOCOYGW5YdpNMB8Rg==,type:str] password: ENC[AES256_GCM,data:/nd3K0ib2beMEBUPy6x33NZSAZotufSFnl6rZoUQ81c=,iv:pDP9yXJc5tIHt665WINYO/GXpIyyiAU9Kb/sxcEQaco=,tag:kNFH26L4fGEwZabrEkdx7w==,type:str] @@ -35,8 +38,8 @@ sops: azure_kv: [] hc_vault: [] age: [] - lastmodified: "2023-08-17T05:07:29Z" - mac: ENC[AES256_GCM,data:CnuanNmQCj6jaOlm68X+byRDaCOWsMLXGEc4yYMI4WfCJvMXzCD70D83aNtSPwoYS/6JBWrhaTG1Hc3jvsMRDSrI0xJKxgoTUwMjO22aYBQhIyXKnT866xQmSKB9bFmAYNMU25bLWIf82QINBGqZhvCF0ugebYnnQnmrS/VuQEI=,iv:FJ3/+0tqj3IKOORbWqRhZit7qLng78hibfTM7KF7Sv8=,tag:1q7rCQMGzlrtvZeTVpdoxA==,type:str] + lastmodified: "2023-12-20T12:43:01Z" + mac: ENC[AES256_GCM,data:9MpmcF8eX3SRyCBqylrteAlVGeOoZzRL3T8ocwiez+YQuvAF3SRL8IfEszbQkRXlAA6BMcVYkXmYbQEj7pNlNrZa5bEw6RlEPDx3ky+F4wbu5ABBGCSu+1uhYW8OV9OTO34WC9hHoZEVHQqfK27ZFbqn+I0waS3oMqmOfCVZ9jI=,iv:j9o5u9JaR3qLzKaqssYMVmnZItiQfLTJn15LkPz4IB4=,tag:Z3ae23INu9ZOhX9doGP/og==,type:str] pgp: [] unencrypted_suffix: _unencrypted version: 3.7.3 diff --git a/pkg/config/config.go b/pkg/config/config.go index 14333dd1..cb49ae51 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -30,7 +30,7 @@ type Config struct { Authentication authentication Groups []group AdminUser user - DefaultUser user + E2eTestUser user S3Bucket string S3Region string S3Endpoint string @@ -93,7 +93,7 @@ func New() Config { }, Groups: newGroups(), AdminUser: newAdminUser(), - DefaultUser: user{}, + E2eTestUser: newE2eTestUser(), S3Bucket: requireEnv("S3_BUCKET"), S3Region: requireEnv("S3_REGION"), S3Endpoint: os.Getenv("S3_ENDPOINT"), @@ -231,6 +231,16 @@ func newAdminUser() user { } } +func newE2eTestUser() user { + email := requireEnv("E2E_TEST_USER_EMAIL") + pw := requireEnv("E2E_TEST_USER_PASSWORD") + + return user{ + Email: email, + Password: pw, + } +} + func requireEnv(key string) string { value, exists := os.LookupEnv(key) if !exists { diff --git a/pkg/group/group_integration_test.go b/pkg/group/group_integration_test.go index 6a798914..fb36d764 100644 --- a/pkg/group/group_integration_test.go +++ b/pkg/group/group_integration_test.go @@ -29,7 +29,7 @@ func TestGroupHandler(t *testing.T) { groupRepository := group.NewRepository(db) groupService := group.NewService(groupRepository, userService) - err := user.CreateAdminUser("admin", "admin", userService, groupService) + err := user.CreateUser("admin", "admin", userService, groupService, model.AdministratorGroupName, "admin") require.NoError(t, err, "failed to create admin user and group") client := inttest.SetupHTTPServer(t, func(engine *gin.Engine) { diff --git a/pkg/model/group.go b/pkg/model/group.go index 77626c42..1b2161c8 100644 --- a/pkg/model/group.go +++ b/pkg/model/group.go @@ -3,6 +3,7 @@ package model import "time" const AdministratorGroupName = "administrators" +const DefaultGroupName = "whoami" // Group domain object defining a group // swagger:model diff --git a/pkg/user/user_integration_test.go b/pkg/user/user_integration_test.go index 232dcbc0..cc337ac1 100644 --- a/pkg/user/user_integration_test.go +++ b/pkg/user/user_integration_test.go @@ -31,7 +31,7 @@ func TestUserHandler(t *testing.T) { groupRepository := group.NewRepository(db) groupService := group.NewService(groupRepository, userService) - err := user.CreateAdminUser("admin", "admin", userService, groupService) + err := user.CreateUser("admin", "admin", userService, groupService, model.AdministratorGroupName, "admin") require.NoError(t, err, "failed to create admin user and group") authorization := middleware.NewAuthorization(userService) diff --git a/pkg/user/util.go b/pkg/user/util.go index 38ca4f13..f79ed027 100644 --- a/pkg/user/util.go +++ b/pkg/user/util.go @@ -16,27 +16,27 @@ type userServiceUtil interface { Save(user *model.User) error } -func CreateAdminUser(email, password string, userService userServiceUtil, groupService groupService) error { +func CreateUser(email, password string, userService userServiceUtil, groupService groupService, groupName, userType string) error { u, err := userService.FindOrCreate(email, password) if err != nil { - return fmt.Errorf("error creating admin user: %v", err) + return fmt.Errorf("error creating %s user: %v", userType, err) } u.Validated = true err = userService.Save(u) if err != nil { - return fmt.Errorf("error saving admin user: %v", err) + return fmt.Errorf("error saving %s user: %v", userType, err) } - g, err := groupService.FindOrCreate(model.AdministratorGroupName, "", false) + g, err := groupService.FindOrCreate(groupName, "", false) if err != nil { - return fmt.Errorf("error creating admin group: %v", err) + return fmt.Errorf("error creating %s group: %v", groupName, err) } err = groupService.AddUser(g.Name, u.ID) if err != nil { - return fmt.Errorf("error adding admin user to admin group: %v", err) + return fmt.Errorf("error adding %s user to %s group: %v", userType, groupName, err) } return nil