Skip to content

feat(zitadel): gitlab oauth and role mapping#1383

Merged
zackpollard merged 1 commit intomainfrom
feat/gitlab-oauth-zitadel
Mar 31, 2026
Merged

feat(zitadel): gitlab oauth and role mapping#1383
zackpollard merged 1 commit intomainfrom
feat/gitlab-oauth-zitadel

Conversation

@zackpollard
Copy link
Copy Markdown
Member

No description provided.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 5, 2026

--- kubernetes/apps Kustomization: flux-system/cluster-apps Kustomization: flux-system/outline-role-sync-secrets

+++ kubernetes/apps Kustomization: flux-system/cluster-apps Kustomization: flux-system/outline-role-sync-secrets

@@ -0,0 +1,25 @@

+---
+apiVersion: kustomize.toolkit.fluxcd.io/v1
+kind: Kustomization
+metadata:
+  labels:
+    kustomize.toolkit.fluxcd.io/name: cluster-apps
+    kustomize.toolkit.fluxcd.io/namespace: flux-system
+  name: outline-role-sync-secrets
+  namespace: flux-system
+spec:
+  commonMetadata:
+    labels:
+      app.kubernetes.io/name: outline-role-sync-secrets
+  dependsOn:
+  - name: external-secrets-stores
+  interval: 30m
+  path: ./kubernetes/apps/tools/outline-role-sync/secrets
+  prune: true
+  retryInterval: 1m
+  sourceRef:
+    kind: GitRepository
+    name: immich-kubernetes
+  timeout: 5m
+  wait: true
+
--- kubernetes/apps Kustomization: flux-system/cluster-apps Kustomization: flux-system/outline-role-sync

+++ kubernetes/apps Kustomization: flux-system/cluster-apps Kustomization: flux-system/outline-role-sync

@@ -0,0 +1,27 @@

+---
+apiVersion: kustomize.toolkit.fluxcd.io/v1
+kind: Kustomization
+metadata:
+  labels:
+    kustomize.toolkit.fluxcd.io/name: cluster-apps
+    kustomize.toolkit.fluxcd.io/namespace: flux-system
+  name: outline-role-sync
+  namespace: flux-system
+spec:
+  commonMetadata:
+    labels:
+      app.kubernetes.io/name: outline-role-sync
+  dependsOn:
+  - name: outline-role-sync-secrets
+  - name: outline
+  interval: 30m
+  path: ./kubernetes/apps/tools/outline-role-sync/app
+  prune: true
+  retryInterval: 1m
+  sourceRef:
+    kind: GitRepository
+    name: immich-kubernetes
+  targetNamespace: tools
+  timeout: 5m
+  wait: true
+
--- kubernetes/apps/tools/outline-role-sync/secrets Kustomization: flux-system/outline-role-sync-secrets ExternalSecret: tools/outline-role-sync

+++ kubernetes/apps/tools/outline-role-sync/secrets Kustomization: flux-system/outline-role-sync-secrets ExternalSecret: tools/outline-role-sync

@@ -0,0 +1,29 @@

+---
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+  labels:
+    app.kubernetes.io/name: outline-role-sync-secrets
+    kustomize.toolkit.fluxcd.io/name: outline-role-sync-secrets
+    kustomize.toolkit.fluxcd.io/namespace: flux-system
+  name: outline-role-sync
+  namespace: tools
+spec:
+  data:
+  - remoteRef:
+      key: OUTLINE_ROLE_SYNC_OUTLINE_API_TOKEN
+    secretKey: OUTLINE_API_TOKEN
+  - remoteRef:
+      key: OUTLINE_ROLE_SYNC_WEBHOOK_SECRET
+    secretKey: OUTLINE_WEBHOOK_SECRET
+  - remoteRef:
+      key: OUTLINE_ROLE_SYNC_ZITADEL_TOKEN
+    secretKey: ZITADEL_SERVICE_ACCOUNT_TOKEN
+  - remoteRef:
+      key: OUTLINE_ROLE_SYNC_ZITADEL_PROJECT_ID
+    secretKey: ZITADEL_OUTLINE_PROJECT_ID
+  refreshInterval: 20s
+  secretStoreRef:
+    kind: ClusterSecretStore
+    name: 1p-tf
+
--- kubernetes/apps/tools/outline-role-sync/app Kustomization: flux-system/outline-role-sync HelmRelease: tools/outline-role-sync

+++ kubernetes/apps/tools/outline-role-sync/app Kustomization: flux-system/outline-role-sync HelmRelease: tools/outline-role-sync

@@ -0,0 +1,71 @@

+---
+apiVersion: helm.toolkit.fluxcd.io/v2
+kind: HelmRelease
+metadata:
+  labels:
+    app.kubernetes.io/name: outline-role-sync
+    kustomize.toolkit.fluxcd.io/name: outline-role-sync
+    kustomize.toolkit.fluxcd.io/namespace: flux-system
+  name: outline-role-sync
+  namespace: tools
+spec:
+  chart:
+    spec:
+      chart: app-template
+      sourceRef:
+        kind: HelmRepository
+        name: bjw-s
+        namespace: flux-system
+      version: 4.6.2
+  install:
+    remediation:
+      retries: 3
+  interval: 30m
+  maxHistory: 2
+  upgrade:
+    cleanupOnFail: true
+    remediation:
+      retries: 3
+      strategy: rollback
+  values:
+    controllers:
+      outline-role-sync:
+        containers:
+          app:
+            env:
+              OUTLINE_BASE_URL: https://outline.immich.cloud
+              PORT: '8080'
+              ZITADEL_BASE_URL: https://zitadel.internal.immich.cloud
+            envFrom:
+            - secretRef:
+                name: outline-role-sync
+            image:
+              repository: ghcr.io/immich-app/outline-role-sync
+              tag: release
+            probes:
+              liveness:
+                custom: true
+                enabled: true
+                spec:
+                  httpGet:
+                    path: /health
+                    port: 8080
+                  periodSeconds: 30
+              readiness:
+                custom: true
+                enabled: true
+                spec:
+                  httpGet:
+                    path: /health
+                    port: 8080
+                  periodSeconds: 10
+    defaultPodOptions:
+      labels:
+        podbump.bo0tzz.me/enabled: 'true'
+    service:
+      app:
+        controller: outline-role-sync
+        ports:
+          http:
+            port: 8080
+

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 5, 2026

Terraform Plan

Shared

1password/account — No changes
1password/futo-account — No changes
cloudflare/account — No changes
cloudflare/api-keys — No changes
docker/org — No changes
github/org — No changes
github/secrets — No changes
github/webhooks — No changes
⚠️ zitadel/cloud — Plan: 10 to add, 2 to change, 0 to destroy.
OpenTofu used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create
  ~ update in-place (current -> planned)
  # onepassword_item.outline_role_sync_zitadel_project_id will be created
  + resource "onepassword_item" "outline_role_sync_zitadel_project_id" {
      + category = "password"
      + id       = (known after apply)
      + password = (sensitive value)
      + title    = "OUTLINE_ROLE_SYNC_ZITADEL_PROJECT_ID"
      + uuid     = (known after apply)
      + vault    = "ilwk2teati3z264z2szn5cwxia"
    }
  # onepassword_item.outline_role_sync_zitadel_token will be created
  + resource "onepassword_item" "outline_role_sync_zitadel_token" {
      + category = "password"
      + id       = (known after apply)
      + password = (sensitive value)
      + title    = "OUTLINE_ROLE_SYNC_ZITADEL_TOKEN"
      + uuid     = (known after apply)
      + vault    = "ilwk2teati3z264z2szn5cwxia"
    }
  # random_password.zitadel_user_initial_password["gitlab-2"] will be created
  + resource "random_password" "zitadel_user_initial_password" {
      + bcrypt_hash = (sensitive value)
      + id          = (known after apply)
      + length      = 64
      + lower       = true
      + min_lower   = 0
      + min_numeric = 0
      + min_special = 0
      + min_upper   = 0
      + number      = true
      + numeric     = true
      + result      = (sensitive value)
      + special     = true
      + upper       = true
    }
  # zitadel_action.map_gitlab_oauth will be created
  + resource "zitadel_action" "map_gitlab_oauth" {
      + allowed_to_fail = true
      + id              = (known after apply)
      + name            = "mapGitLabOAuth"
      + org_id          = "364798781738212816"
      + script          = (known after apply)
      + state           = (known after apply)
      + timeout         = "10s"
    }
  # zitadel_default_login_policy.default will be updated in-place
  ~ resource "zitadel_default_login_policy" "default" {
        id                            = "364772529186811746"
      ~ idps                          = [
          - "364798781687881168",
        ] -> (known after apply)
        # (18 unchanged attributes hidden)
    }
  # zitadel_human_user.users["gitlab-2"] will be created
  + resource "zitadel_human_user" "users" {
      + display_name                 = "eron last_name"
      + email                        = "eron@gitlab.futo.org"
      + first_name                   = "eron"
      + gender                       = "GENDER_UNSPECIFIED"
      + id                           = (known after apply)
      + initial_password             = (sensitive value)
      + initial_skip_password_change = true
      + is_email_verified            = true
      + is_phone_verified            = false
      + last_name                    = "last_name"
      + login_names                  = (known after apply)
      + org_id                       = "364798781738212816"
      + preferred_language           = "und"
      + preferred_login_name         = (known after apply)
      + state                        = (known after apply)
      + user_id                      = (known after apply)
      + user_name                    = "eron"
    }
  # zitadel_idp_gitlab_self_hosted.gitlab will be created
  + resource "zitadel_idp_gitlab_self_hosted" "gitlab" {
      + auto_linking        = "AUTO_LINKING_OPTION_USERNAME"
      + client_id           = "<concealed by 1Password>"
      + client_secret       = (sensitive value)
      + id                  = (known after apply)
      + is_auto_creation    = false
      + is_auto_update      = true
      + is_creation_allowed = false
      + is_linking_allowed  = true
      + issuer              = "<concealed by 1Password>"
      + name                = "FUTO GitLab"
      + scopes              = [
          + "email",
          + "openid",
          + "profile",
        ]
    }
  # zitadel_machine_user.outline_role_sync will be created
  + resource "zitadel_machine_user" "outline_role_sync" {
      + access_token_type    = "ACCESS_TOKEN_TYPE_BEARER"
      + client_id            = (sensitive value)
      + client_secret        = (sensitive value)
      + description          = "Service account for syncing Zitadel roles to Outline groups"
      + id                   = (known after apply)
      + login_names          = (known after apply)
      + name                 = "Outline Role Sync"
      + org_id               = "364798781738212816"
      + preferred_login_name = (known after apply)
      + state                = (known after apply)
      + user_id              = (known after apply)
      + user_name            = "outline-role-sync"
      + with_secret          = false
    }
  # zitadel_org_member.outline_role_sync will be created
  + resource "zitadel_org_member" "outline_role_sync" {
      + id      = (known after apply)
      + org_id  = "364798781738212816"
      + roles   = [
          + "ORG_USER_MANAGER",
        ]
      + user_id = (known after apply)
    }
  # zitadel_personal_access_token.outline_role_sync will be created
  + resource "zitadel_personal_access_token" "outline_role_sync" {
      + expiration_date = "2030-01-01T00:00:00Z"
      + id              = (known after apply)
      + org_id          = "364798781738212816"
      + token           = (sensitive value)
      + user_id         = (known after apply)
    }
  # zitadel_trigger_actions.map_external_oauth will be updated in-place
  # (moved from zitadel_trigger_actions.map_github_oauth)
  ~ resource "zitadel_trigger_actions" "map_external_oauth" {
      ~ action_ids   = [
          - "364798782275214800",
        ] -> (known after apply)
        id           = "364798781738212816_FLOW_TYPE_EXTERNAL_AUTHENTICATION_TRIGGER_TYPE_POST_AUTHENTICATION"
        # (3 unchanged attributes hidden)
    }
  # zitadel_user_metadata.role["gitlab-2"] will be created
  + resource "zitadel_user_metadata" "role" {
      + id      = (known after apply)
      + key     = "role"
      + org_id  = "364798781738212816"
      + user_id = (known after apply)
      + value   = jsonencode(
            [
              + "futo",
            ]
        )
    }
Plan: 10 to add, 2 to change, 0 to destroy.
─────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so OpenTofu can't
guarantee to take exactly these actions if you run "tofu apply" now.
zitadel/self-hosted — Error
│ Error: Invalid for_each argument
│ 
│   on users.tf line 6, in resource "random_password" "zitadel_user_initial_password":
│    6:   for_each = {
│    7:     for user in local.users_data : user.github.id => user
│    8:     if user.github.username != null && user.github.username != ""
--
│ Error: Attempt to get attribute from null value
│ 
│   on users.tf line 8, in resource "random_password" "zitadel_user_initial_password":
│    8:     if user.github.username != null && user.github.username != ""
│     ├────────────────
│     │ user.github is null
--
│ Error: Attempt to get attribute from null value
│ 
│   on users.tf line 8, in resource "random_password" "zitadel_user_initial_password":
│    8:     if user.github.username != null && user.github.username != ""
│     ├────────────────
│     │ user.github is null

Scoped (dev)

discord/community — No changes
monitoring/grafana — No changes

Scoped (prod)

discord/community — No changes
monitoring/grafana — No changes

Base automatically changed from chore/1password-secrets to main March 5, 2026 03:38
@zackpollard zackpollard force-pushed the feat/gitlab-oauth-zitadel branch 3 times, most recently from 3d3a5d8 to f1804aa Compare March 30, 2026 10:45
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 30, 2026

Terraform Plan

Shared

1password/account — No changes
1password/futo-account — No changes
cloudflare/account — No changes
cloudflare/api-keys — No changes
docker/org — No changes
⚠️ github/org — Plan: 0 to add, 6 to change, 0 to destroy.
OpenTofu used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  ~ update in-place (current -> planned)
  # github_repository_file.default_files["base-images/.github/workflows/org-pr-require-conventional-commit.yml"] will be updated in-place
  ~ resource "github_repository_file" "default_files" {
      ~ content             = <<-EOT
            name: PR Conventional Commit
            on:
              pull_request:
                types: [opened, synchronize, reopened, edited]
            jobs:
              validate-pr-title:
                name: Validate PR Title (conventional commit)
          -     uses: <concealed by 1Password>/devtools/.github/workflows/shared-pr-require-conventional-commit.yml@464be4a34cf24db195553b91983174e21cc5d55b # main
          +     uses: <concealed by 1Password>/devtools/.github/workflows/shared-pr-require-conventional-commit.yml@main
                permissions:
                  pull-requests: write
        EOT
        id                  = "base-images:.github/workflows/org-pr-require-conventional-commit.yml:main"
        # (9 unchanged attributes hidden)
    }
  # github_repository_file.default_files["base-images/.github/workflows/org-zizmor.yml"] will be updated in-place
  ~ resource "github_repository_file" "default_files" {
      ~ content             = <<-EOT
            name: Zizmor
            on:
              pull_request:
              push:
                branches: [main]
            jobs:
              zizmor:
                name: Zizmor
          -     uses: <concealed by 1Password>/devtools/.github/workflows/shared-zizmor.yml@464be4a34cf24db195553b91983174e21cc5d55b # main
          +     uses: <concealed by 1Password>/devtools/.github/workflows/shared-zizmor.yml@main
                permissions:
                  actions: read
                  contents: read
                  security-events: write
        EOT
        id                  = "base-images:.github/workflows/org-zizmor.yml:main"
        # (9 unchanged attributes hidden)
    }
  # github_repository_file.default_files["immich-charts/.github/workflows/org-pr-require-conventional-commit.yml"] will be updated in-place
  ~ resource "github_repository_file" "default_files" {
      ~ content             = <<-EOT
            name: PR Conventional Commit
            on:
              pull_request:
                types: [opened, synchronize, reopened, edited]
            jobs:
              validate-pr-title:
                name: Validate PR Title (conventional commit)
          -     uses: <concealed by 1Password>/devtools/.github/workflows/shared-pr-require-conventional-commit.yml@f88698ef64e40a764b4b7a81eae5f76634f53a6c # main
          +     uses: <concealed by 1Password>/devtools/.github/workflows/shared-pr-require-conventional-commit.yml@main
                permissions:
                  pull-requests: write
        EOT
        id                  = "immich-charts:.github/workflows/org-pr-require-conventional-commit.yml:main"
        # (9 unchanged attributes hidden)
    }
  # github_repository_file.default_files["immich-charts/.github/workflows/org-zizmor.yml"] will be updated in-place
  ~ resource "github_repository_file" "default_files" {
      ~ content             = <<-EOT
            name: Zizmor
            on:
              pull_request:
              push:
                branches: [main]
            jobs:
              zizmor:
                name: Zizmor
          -     uses: <concealed by 1Password>/devtools/.github/workflows/shared-zizmor.yml@f88698ef64e40a764b4b7a81eae5f76634f53a6c # main
          +     uses: <concealed by 1Password>/devtools/.github/workflows/shared-zizmor.yml@main
                permissions:
                  actions: read
                  contents: read
                  security-events: write
        EOT
        id                  = "immich-charts:.github/workflows/org-zizmor.yml:main"
        # (9 unchanged attributes hidden)
    }
  # github_repository_file.default_files["ui/.github/workflows/org-pr-require-conventional-commit.yml"] will be updated in-place
  ~ resource "github_repository_file" "default_files" {
      ~ content             = <<-EOT
            name: PR Conventional Commit
            on:
              pull_request:
                types: [opened, synchronize, reopened, edited]
            jobs:
              validate-pr-title:
                name: Validate PR Title (conventional commit)
          -     uses: <concealed by 1Password>/devtools/.github/workflows/shared-pr-require-conventional-commit.yml@d6597891f4ae2a8ca157c4fe4fc04875ff34b9ea # main
          +     uses: <concealed by 1Password>/devtools/.github/workflows/shared-pr-require-conventional-commit.yml@main
                permissions:
                  pull-requests: write
        EOT
        id                  = "ui:.github/workflows/org-pr-require-conventional-commit.yml:main"
        # (9 unchanged attributes hidden)
    }
  # github_repository_file.default_files["ui/.github/workflows/org-zizmor.yml"] will be updated in-place
  ~ resource "github_repository_file" "default_files" {
      ~ content             = <<-EOT
            name: Zizmor
            on:
              pull_request:
              push:
                branches: [main]
            jobs:
              zizmor:
                name: Zizmor
          -     uses: <concealed by 1Password>/devtools/.github/workflows/shared-zizmor.yml@d6597891f4ae2a8ca157c4fe4fc04875ff34b9ea # main
          +     uses: <concealed by 1Password>/devtools/.github/workflows/shared-zizmor.yml@main
                permissions:
                  actions: read
                  contents: read
                  security-events: write
        EOT
        id                  = "ui:.github/workflows/org-zizmor.yml:main"
        # (9 unchanged attributes hidden)
    }
Plan: 0 to add, 6 to change, 0 to destroy.
╷
│ Warning: Argument is deprecated
│ 
│   with github_repository.repositories,
│   on repositories.tf line 170, in resource "github_repository" "repositories":170:   has_downloads             = true
│ 
│ This attribute is no longer in use, but it hasn't been removed yet. It will
│ be removed in a future version. See
│ https://github.com/orgs/community/discussions/102145#discussioncomment-8351756
│ 
│ (and 26 more similar warnings elsewhere)
╵
─────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so OpenTofu can't
guarantee to take exactly these actions if you run "tofu apply" now.
github/secrets — No changes
github/webhooks — No changes
⚠️ zitadel/cloud — Plan: 10 to add, 2 to change, 0 to destroy.
OpenTofu used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create
  ~ update in-place (current -> planned)
  # onepassword_item.outline_role_sync_zitadel_project_id will be created
  + resource "onepassword_item" "outline_role_sync_zitadel_project_id" {
      + category = "password"
      + id       = (known after apply)
      + password = (sensitive value)
      + title    = "OUTLINE_ROLE_SYNC_ZITADEL_PROJECT_ID"
      + uuid     = (known after apply)
      + vault    = "ilwk2teati3z264z2szn5cwxia"
    }
  # onepassword_item.outline_role_sync_zitadel_token will be created
  + resource "onepassword_item" "outline_role_sync_zitadel_token" {
      + category = "password"
      + id       = (known after apply)
      + password = (sensitive value)
      + title    = "OUTLINE_ROLE_SYNC_ZITADEL_TOKEN"
      + uuid     = (known after apply)
      + vault    = "ilwk2teati3z264z2szn5cwxia"
    }
  # random_password.zitadel_user_initial_password["gitlab-2"] will be created
  + resource "random_password" "zitadel_user_initial_password" {
      + bcrypt_hash = (sensitive value)
      + id          = (known after apply)
      + length      = 64
      + lower       = true
      + min_lower   = 0
      + min_numeric = 0
      + min_special = 0
      + min_upper   = 0
      + number      = true
      + numeric     = true
      + result      = (sensitive value)
      + special     = true
      + upper       = true
    }
  # zitadel_action.map_gitlab_oauth will be created
  + resource "zitadel_action" "map_gitlab_oauth" {
      + allowed_to_fail = true
      + id              = (known after apply)
      + name            = "mapGitLabOAuth"
      + org_id          = "364798781738212816"
      + script          = (known after apply)
      + state           = (known after apply)
      + timeout         = "10s"
    }
  # zitadel_default_login_policy.default will be updated in-place
  ~ resource "zitadel_default_login_policy" "default" {
        id                            = "364772529186811746"
      ~ idps                          = [
          - "364798781687881168",
        ] -> (known after apply)
        # (18 unchanged attributes hidden)
    }
  # zitadel_human_user.users["gitlab-2"] will be created
  + resource "zitadel_human_user" "users" {
      + display_name                 = "eron last_name"
      + email                        = "eron@gitlab.futo.org"
      + first_name                   = "eron"
      + gender                       = "GENDER_UNSPECIFIED"
      + id                           = (known after apply)
      + initial_password             = (sensitive value)
      + initial_skip_password_change = true
      + is_email_verified            = true
      + is_phone_verified            = false
      + last_name                    = "last_name"
      + login_names                  = (known after apply)
      + org_id                       = "364798781738212816"
      + preferred_language           = "und"
      + preferred_login_name         = (known after apply)
      + state                        = (known after apply)
      + user_id                      = (known after apply)
      + user_name                    = "eron"
    }
  # zitadel_idp_gitlab_self_hosted.gitlab will be created
  + resource "zitadel_idp_gitlab_self_hosted" "gitlab" {
      + auto_linking        = "AUTO_LINKING_OPTION_USERNAME"
      + client_id           = "<concealed by 1Password>"
      + client_secret       = (sensitive value)
      + id                  = (known after apply)
      + is_auto_creation    = false
      + is_auto_update      = true
      + is_creation_allowed = false
      + is_linking_allowed  = true
      + issuer              = "<concealed by 1Password>"
      + name                = "FUTO GitLab"
      + scopes              = [
          + "email",
          + "openid",
          + "profile",
        ]
    }
  # zitadel_machine_user.outline_role_sync will be created
  + resource "zitadel_machine_user" "outline_role_sync" {
      + access_token_type    = "ACCESS_TOKEN_TYPE_BEARER"
      + client_id            = (sensitive value)
      + client_secret        = (sensitive value)
      + description          = "Service account for syncing Zitadel roles to Outline groups"
      + id                   = (known after apply)
      + login_names          = (known after apply)
      + name                 = "Outline Role Sync"
      + org_id               = "364798781738212816"
      + preferred_login_name = (known after apply)
      + state                = (known after apply)
      + user_id              = (known after apply)
      + user_name            = "outline-role-sync"
      + with_secret          = false
    }
  # zitadel_org_member.outline_role_sync will be created
  + resource "zitadel_org_member" "outline_role_sync" {
      + id      = (known after apply)
      + org_id  = "364798781738212816"
      + roles   = [
          + "ORG_USER_MANAGER",
        ]
      + user_id = (known after apply)
    }
  # zitadel_personal_access_token.outline_role_sync will be created
  + resource "zitadel_personal_access_token" "outline_role_sync" {
      + expiration_date = "2030-01-01T00:00:00Z"
      + id              = (known after apply)
      + org_id          = "364798781738212816"
      + token           = (sensitive value)
      + user_id         = (known after apply)
    }
  # zitadel_trigger_actions.map_external_oauth will be updated in-place
  # (moved from zitadel_trigger_actions.map_github_oauth)
  ~ resource "zitadel_trigger_actions" "map_external_oauth" {
      ~ action_ids   = [
          - "364798782275214800",
        ] -> (known after apply)
        id           = "364798781738212816_FLOW_TYPE_EXTERNAL_AUTHENTICATION_TRIGGER_TYPE_POST_AUTHENTICATION"
        # (3 unchanged attributes hidden)
    }
  # zitadel_user_metadata.role["gitlab-2"] will be created
  + resource "zitadel_user_metadata" "role" {
      + id      = (known after apply)
      + key     = "role"
      + org_id  = "364798781738212816"
      + user_id = (known after apply)
      + value   = jsonencode(
            [
              + "futo",
            ]
        )
    }
Plan: 10 to add, 2 to change, 0 to destroy.
─────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so OpenTofu can't
guarantee to take exactly these actions if you run "tofu apply" now.
zitadel/self-hosted — Error
│ Error: Invalid for_each argument
│ 
│   on users.tf line 6, in resource "random_password" "zitadel_user_initial_password":
│    6:   for_each = {
│    7:     for user in local.users_data : user.github.id => user
│    8:     if user.github.username != null && user.github.username != ""
--
│ Error: Attempt to get attribute from null value
│ 
│   on users.tf line 8, in resource "random_password" "zitadel_user_initial_password":
│    8:     if user.github.username != null && user.github.username != ""
│     ├────────────────
│     │ user.github is null
--
│ Error: Attempt to get attribute from null value
│ 
│   on users.tf line 8, in resource "random_password" "zitadel_user_initial_password":
│    8:     if user.github.username != null && user.github.username != ""
│     ├────────────────
│     │ user.github is null

Scoped (dev)

discord/community — No changes
monitoring/grafana — No changes

Scoped (prod)

⚠️ discord/community — Plan: 0 to add, 63 to change, 0 to destroy.
OpenTofu used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  ~ update in-place (current -> planned)
  # discord_forum_channel.dev_focus_topic will be updated in-place
  ~ resource "discord_forum_channel" "dev_focus_topic" {
        id                       = "1045707766754451486"
        name                     = "dev-focus-topic"
      ~ position                 = 22 -> 2
        # (5 unchanged attributes hidden)
    }
  # discord_forum_channel.draft_announcements will be updated in-place
  ~ resource "discord_forum_channel" "draft_announcements" {
        id                       = "1073000522338017381"
        name                     = "draft-announcements"
      ~ position                 = 19 -> 1
        # (5 unchanged attributes hidden)
    }
  # discord_forum_channel.focus_discussion will be updated in-place
  ~ resource "discord_forum_channel" "focus_discussion" {
        id                       = "1026327300284887111"
        name                     = "focus-discussion"
      ~ position                 = 6 -> 2
        # (5 unchanged attributes hidden)
    }
  # discord_forum_channel.help_desk_support will be updated in-place
  ~ resource "discord_forum_channel" "help_desk_support" {
        id                       = "1049703391762321418"
        name                     = "help-desk-support"
      ~ position                 = 4 -> 0
        # (5 unchanged attributes hidden)
    }
  # discord_forum_channel.leadership_focus_topic will be updated in-place
  ~ resource "discord_forum_channel" "leadership_focus_topic" {
        id                       = "1229454284479795291"
        name                     = "leadership-focus-topic"
      ~ position                 = 39 -> 3
        # (5 unchanged attributes hidden)
    }
  # discord_forum_channel.team_focus_topic will be updated in-place
  ~ resource "discord_forum_channel" "team_focus_topic" {
        id                       = "1330248543721754746"
        name                     = "team-focus-topic"
      ~ position                 = 26 -> 3
        # (5 unchanged attributes hidden)
    }
  # discord_forum_channel.team_pull_requests will be updated in-place
  ~ resource "discord_forum_channel" "team_pull_requests" {
        id                       = "1476975523271016661"
        name                     = "team-pull-requests"
      ~ position                 = 27 -> 4
        # (6 unchanged attributes hidden)
    }
  # discord_forum_channel.yucca_focus_topic will be updated in-place
  ~ resource "discord_forum_channel" "yucca_focus_topic" {
        id                       = "1413525610700996739"
        name                     = "yucca-focus-topic"
      ~ position                 = 32 -> 2
        # (6 unchanged attributes hidden)
    }
  # discord_news_channel.github_releases will be updated in-place
  ~ resource "discord_news_channel" "github_releases" {
        id                       = "991477056791658567"
        name                     = "github-releases"
      ~ position                 = 46 -> 5
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.bot_spam will be updated in-place
  ~ resource "discord_text_channel" "bot_spam" {
        id                       = "1159083520027787307"
        name                     = "bot-spam"
      ~ position                 = 47 -> 0
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.branding will be updated in-place
  ~ resource "discord_text_channel" "branding" {
        id                       = "1215143314358009866"
        name                     = "branding"
      ~ position                 = 54 -> 4
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.build_status will be updated in-place
  ~ resource "discord_text_channel" "build_status" {
        id                       = "981216275210584134"
        name                     = "build-status"
      ~ position                 = 66 -> 16
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.cloudflare_status will be updated in-place
  ~ resource "discord_text_channel" "cloudflare_status" {
        id                       = "1313493521755410443"
        name                     = "cloudflare-status"
      ~ position                 = 42 -> 1
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.conference_room will be updated in-place
  ~ resource "discord_text_channel" "conference_room" {
        id                       = "1206801539470069810"
        name                     = "conference-room"
      ~ position                 = 53 -> 3
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.contributing will be updated in-place
  ~ resource "discord_text_channel" "contributing" {
        id                       = "1071165397228855327"
        name                     = "contributing"
      ~ position                 = 7 -> 3
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.dev will be updated in-place
  ~ resource "discord_text_channel" "dev" {
        id                       = "979148343693422593"
        name                     = "dev"
      ~ position                 = 20 -> 0
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.dev_cli will be updated in-place
  ~ resource "discord_text_channel" "dev_cli" {
        id                       = "1175153422866059329"
        name                     = "dev-cli"
      ~ position                 = 64 -> 14
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.dev_fosdem will be updated in-place
  ~ resource "discord_text_channel" "dev_fosdem" {
        id                       = "1291108934861586492"
        name                     = "dev-fosdem"
      ~ position                 = 50 -> 0
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.dev_ml will be updated in-place
  ~ resource "discord_text_channel" "dev_ml" {
        id                       = "1175513104877101078"
        name                     = "dev-ml"
      ~ position                 = 55 -> 5
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.dev_mobile will be updated in-place
  ~ resource "discord_text_channel" "dev_mobile" {
        id                       = "1069026079349669951"
        name                     = "dev-mobile"
      ~ position                 = 58 -> 8
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.dev_off_topic will be updated in-place
  ~ resource "discord_text_channel" "dev_off_topic" {
        id                       = "1034520166115053638"
        name                     = "dev-off-topic"
      ~ position                 = 21 -> 1
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.dev_ops will be updated in-place
  ~ resource "discord_text_channel" "dev_ops" {
        id                       = "1176633064462487686"
        name                     = "dev-ops"
      ~ position                 = 61 -> 11
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.dev_roles will be updated in-place
  ~ resource "discord_text_channel" "dev_roles" {
        id                       = "1196526624812839034"
        name                     = "dev-roles"
      ~ position                 = 59 -> 9
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.dev_security will be updated in-place
  ~ resource "discord_text_channel" "dev_security" {
        id                       = "1020479445167001650"
        name                     = "dev-security"
      ~ position                 = 60 -> 10
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.dev_server will be updated in-place
  ~ resource "discord_text_channel" "dev_server" {
        id                       = "1155894065062236160"
        name                     = "dev-server"
      ~ position                 = 52 -> 2
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.dev_web will be updated in-place
  ~ resource "discord_text_channel" "dev_web" {
        id                       = "1169697266806820974"
        name                     = "dev-web"
      ~ position                 = 63 -> 13
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.developer_updates will be updated in-place
  ~ resource "discord_text_channel" "developer_updates" {
        id                       = "1361300925788192778"
        name                     = "developer-updates"
      ~ position                 = 41 -> 0
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.emotes will be updated in-place
  ~ resource "discord_text_channel" "emotes" {
        id                       = "1287169306244943894"
        name                     = "emotes"
      ~ position                 = 48 -> 1
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.fosdem_2025 will be updated in-place
  ~ resource "discord_text_channel" "fosdem_2025" {
        id                       = "1334949885442654248"
        name                     = "fosdem-2025"
      ~ position                 = 51 -> 1
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.futo will be updated in-place
  ~ resource "discord_text_channel" "futo" {
        id                       = "1208144146825748491"
        name                     = "futo"
      ~ position                 = 56 -> 6
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.futo_discussion_old will be updated in-place
  ~ resource "discord_text_channel" "futo_discussion_old" {
        id                       = "1238236317729554523"
        name                     = "futo-discussion-old"
      ~ position                 = 62 -> 12
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.github_issues_and_discussion will be updated in-place
  ~ resource "discord_text_channel" "github_issues_and_discussion" {
        id                       = "991483015958106202"
        name                     = "github-issues-and-discussion"
      ~ position                 = 44 -> 3
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.github_pull_requests will be updated in-place
  ~ resource "discord_text_channel" "github_pull_requests" {
        id                       = "991483093179445350"
        name                     = "github-pull-requests"
      ~ position                 = 45 -> 4
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.github_status will be updated in-place
  ~ resource "discord_text_channel" "github_status" {
        id                       = "1240662502912692236"
        name                     = "github-status"
      ~ position                 = 43 -> 2
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.imagegenius_aio will be updated in-place
  ~ resource "discord_text_channel" "imagegenius_aio" {
        id                       = "1178366211675930634"
        name                     = "imagegenius-aio"
      ~ position                 = 11 -> 0
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.immich will be updated in-place
  ~ resource "discord_text_channel" "immich" {
        id                       = "994044917355663450"
        name                     = "immich"
      ~ position                 = 9 -> 5
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.immich_frame will be updated in-place
  ~ resource "discord_text_channel" "immich_frame" {
        id                       = "1217843270244372480"
        name                     = "immich-frame"
      ~ position                 = 13 -> 2
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.immich_go will be updated in-place
  ~ resource "discord_text_channel" "immich_go" {
        id                       = "1178366369423700080"
        name                     = "immich-go"
      ~ position                 = 12 -> 1
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.immich_kiosk will be updated in-place
  ~ resource "discord_text_channel" "immich_kiosk" {
        id                       = "1293191523927851099"
        name                     = "immich-kiosk"
      ~ position                 = 14 -> 3
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.immich_nix will be updated in-place
  ~ resource "discord_text_channel" "immich_nix" {
        id                       = "1288786177952059435"
        name                     = "immich-nix"
      ~ position                 = 57 -> 7
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.immich_power_tools will be updated in-place
  ~ resource "discord_text_channel" "immich_power_tools" {
        id                       = "1278195895594258553"
        name                     = "immich-power-tools"
      ~ position                 = 15 -> 4
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.jasons_adventures_with_unraid_docker_and_networking will be updated in-place
  ~ resource "discord_text_channel" "jasons_adventures_with_unraid_docker_and_networking" {
        id                       = "1230989262325813270"
        name                     = "jasons-adventures-with-unraid-docker-and-networking"
      ~ position                 = 65 -> 15
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.leadership will be updated in-place
  ~ resource "discord_text_channel" "leadership" {
        id                       = "1176686059422232636"
        name                     = "leadership"
      ~ position                 = 36 -> 0
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.leadership_alerts will be updated in-place
  ~ resource "discord_text_channel" "leadership_alerts" {
        id                       = "1260975801625608213"
        name                     = "leadership-alerts"
      ~ position                 = 38 -> 2
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.leadership_off_topic will be updated in-place
  ~ resource "discord_text_channel" "leadership_off_topic" {
        id                       = "1255863786686906489"
        name                     = "leadership-off-topic"
      ~ position                 = 37 -> 1
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.merch will be updated in-place
  ~ resource "discord_text_channel" "merch" {
        id                       = "1336794023888818288"
        name                     = "merch"
      ~ position                 = 10 -> 6
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.moderator_only will be updated in-place
  ~ resource "discord_text_channel" "moderator_only" {
        id                       = "991930223991988255"
        name                     = "moderator-only"
      ~ position                 = 40 -> 4
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.off_topic will be updated in-place
  ~ resource "discord_text_channel" "off_topic" {
        id                       = "991643870523830382"
        name                     = "off-topic"
      ~ position                 = 49 -> 2
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.package_maintainers will be updated in-place
  ~ resource "discord_text_channel" "package_maintainers" {
        id                       = "1288859036015398974"
        name                     = "package-maintainers"
      ~ position                 = 5 -> 1
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.support_crew will be updated in-place
  ~ resource "discord_text_channel" "support_crew" {
        id                       = "1184258493948117084"
        name                     = "support-crew"
      ~ position                 = 18 -> 0
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.team will be updated in-place
  ~ resource "discord_text_channel" "team" {
        id                       = "1330248080271999086"
        name                     = "team"
      ~ position                 = 23 -> 0
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.team_alerts will be updated in-place
  ~ resource "discord_text_channel" "team_alerts" {
        id                       = "1360190417643110460"
        name                     = "team-alerts"
      ~ position                 = 29 -> 6
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.team_off_topic will be updated in-place
  ~ resource "discord_text_channel" "team_off_topic" {
        id                       = "1330252547751022632"
        name                     = "team-off-topic"
      ~ position                 = 25 -> 2
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.team_purchases will be updated in-place
  ~ resource "discord_text_channel" "team_purchases" {
        id                       = "1263492970691297300"
        name                     = "team-purchases"
      ~ position                 = 28 -> 5
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.team_slop will be updated in-place
  ~ resource "discord_text_channel" "team_slop" {
        id                       = "1485644163247640657"
        name                     = "team-slop"
      ~ position                 = 24 -> 1
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.translations will be updated in-place
  ~ resource "discord_text_channel" "translations" {
        id                       = "1250427404976132138"
        name                     = "translations"
      ~ position                 = 8 -> 4
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.truenas will be updated in-place
  ~ resource "discord_text_channel" "truenas" {
        id                       = "1178410588821524561"
        name                     = "truenas"
      ~ position                 = 16 -> 5
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.unraid will be updated in-place
  ~ resource "discord_text_channel" "unraid" {
        id                       = "1228387901889445989"
        name                     = "unraid"
      ~ position                 = 17 -> 6
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.yucca will be updated in-place
  ~ resource "discord_text_channel" "yucca" {
        id                       = "1413525175747608688"
        name                     = "yucca"
      ~ position                 = 30 -> 0
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.yucca_alerts will be updated in-place
  ~ resource "discord_text_channel" "yucca_alerts" {
        id                       = "1413525667429093469"
        name                     = "yucca-alerts"
      ~ position                 = 33 -> 3
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.yucca_alerts_dev will be updated in-place
  ~ resource "discord_text_channel" "yucca_alerts_dev" {
        id                       = "1425822463736021134"
        name                     = "yucca-alerts-dev"
      ~ position                 = 35 -> 5
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.yucca_alerts_testing will be updated in-place
  ~ resource "discord_text_channel" "yucca_alerts_testing" {
        id                       = "1425822461890662460"
        name                     = "yucca-alerts-testing"
      ~ position                 = 34 -> 4
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.yucca_off_topic will be updated in-place
  ~ resource "discord_text_channel" "yucca_off_topic" {
        id                       = "1413525269192376413"
        name                     = "yucca-off-topic"
      ~ position                 = 31 -> 1
        # (6 unchanged attributes hidden)
    }
Plan: 0 to add, 63 to change, 0 to destroy.
─────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so OpenTofu can't
guarantee to take exactly these actions if you run "tofu apply" now.
monitoring/grafana — No changes

@zackpollard zackpollard force-pushed the feat/gitlab-oauth-zitadel branch 2 times, most recently from 111ad5f to da4eec2 Compare March 30, 2026 10:54
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 30, 2026

Terraform Plan

Shared

1password/account — No changes
1password/futo-account — No changes
cloudflare/account — No changes
cloudflare/api-keys — No changes
docker/org — No changes
⚠️ github/org — Plan: 0 to add, 6 to change, 0 to destroy.
OpenTofu used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  ~ update in-place (current -> planned)
  # github_repository_file.default_files["base-images/.github/workflows/org-pr-require-conventional-commit.yml"] will be updated in-place
  ~ resource "github_repository_file" "default_files" {
      ~ content             = <<-EOT
            name: PR Conventional Commit
            on:
              pull_request:
                types: [opened, synchronize, reopened, edited]
            jobs:
              validate-pr-title:
                name: Validate PR Title (conventional commit)
          -     uses: <concealed by 1Password>/devtools/.github/workflows/shared-pr-require-conventional-commit.yml@464be4a34cf24db195553b91983174e21cc5d55b # main
          +     uses: <concealed by 1Password>/devtools/.github/workflows/shared-pr-require-conventional-commit.yml@main
                permissions:
                  pull-requests: write
        EOT
        id                  = "base-images:.github/workflows/org-pr-require-conventional-commit.yml:main"
        # (9 unchanged attributes hidden)
    }
  # github_repository_file.default_files["base-images/.github/workflows/org-zizmor.yml"] will be updated in-place
  ~ resource "github_repository_file" "default_files" {
      ~ content             = <<-EOT
            name: Zizmor
            on:
              pull_request:
              push:
                branches: [main]
            jobs:
              zizmor:
                name: Zizmor
          -     uses: <concealed by 1Password>/devtools/.github/workflows/shared-zizmor.yml@464be4a34cf24db195553b91983174e21cc5d55b # main
          +     uses: <concealed by 1Password>/devtools/.github/workflows/shared-zizmor.yml@main
                permissions:
                  actions: read
                  contents: read
                  security-events: write
        EOT
        id                  = "base-images:.github/workflows/org-zizmor.yml:main"
        # (9 unchanged attributes hidden)
    }
  # github_repository_file.default_files["immich-charts/.github/workflows/org-pr-require-conventional-commit.yml"] will be updated in-place
  ~ resource "github_repository_file" "default_files" {
      ~ content             = <<-EOT
            name: PR Conventional Commit
            on:
              pull_request:
                types: [opened, synchronize, reopened, edited]
            jobs:
              validate-pr-title:
                name: Validate PR Title (conventional commit)
          -     uses: <concealed by 1Password>/devtools/.github/workflows/shared-pr-require-conventional-commit.yml@f88698ef64e40a764b4b7a81eae5f76634f53a6c # main
          +     uses: <concealed by 1Password>/devtools/.github/workflows/shared-pr-require-conventional-commit.yml@main
                permissions:
                  pull-requests: write
        EOT
        id                  = "immich-charts:.github/workflows/org-pr-require-conventional-commit.yml:main"
        # (9 unchanged attributes hidden)
    }
  # github_repository_file.default_files["immich-charts/.github/workflows/org-zizmor.yml"] will be updated in-place
  ~ resource "github_repository_file" "default_files" {
      ~ content             = <<-EOT
            name: Zizmor
            on:
              pull_request:
              push:
                branches: [main]
            jobs:
              zizmor:
                name: Zizmor
          -     uses: <concealed by 1Password>/devtools/.github/workflows/shared-zizmor.yml@f88698ef64e40a764b4b7a81eae5f76634f53a6c # main
          +     uses: <concealed by 1Password>/devtools/.github/workflows/shared-zizmor.yml@main
                permissions:
                  actions: read
                  contents: read
                  security-events: write
        EOT
        id                  = "immich-charts:.github/workflows/org-zizmor.yml:main"
        # (9 unchanged attributes hidden)
    }
  # github_repository_file.default_files["ui/.github/workflows/org-pr-require-conventional-commit.yml"] will be updated in-place
  ~ resource "github_repository_file" "default_files" {
      ~ content             = <<-EOT
            name: PR Conventional Commit
            on:
              pull_request:
                types: [opened, synchronize, reopened, edited]
            jobs:
              validate-pr-title:
                name: Validate PR Title (conventional commit)
          -     uses: <concealed by 1Password>/devtools/.github/workflows/shared-pr-require-conventional-commit.yml@d6597891f4ae2a8ca157c4fe4fc04875ff34b9ea # main
          +     uses: <concealed by 1Password>/devtools/.github/workflows/shared-pr-require-conventional-commit.yml@main
                permissions:
                  pull-requests: write
        EOT
        id                  = "ui:.github/workflows/org-pr-require-conventional-commit.yml:main"
        # (9 unchanged attributes hidden)
    }
  # github_repository_file.default_files["ui/.github/workflows/org-zizmor.yml"] will be updated in-place
  ~ resource "github_repository_file" "default_files" {
      ~ content             = <<-EOT
            name: Zizmor
            on:
              pull_request:
              push:
                branches: [main]
            jobs:
              zizmor:
                name: Zizmor
          -     uses: <concealed by 1Password>/devtools/.github/workflows/shared-zizmor.yml@d6597891f4ae2a8ca157c4fe4fc04875ff34b9ea # main
          +     uses: <concealed by 1Password>/devtools/.github/workflows/shared-zizmor.yml@main
                permissions:
                  actions: read
                  contents: read
                  security-events: write
        EOT
        id                  = "ui:.github/workflows/org-zizmor.yml:main"
        # (9 unchanged attributes hidden)
    }
Plan: 0 to add, 6 to change, 0 to destroy.
╷
│ Warning: Argument is deprecated
│ 
│   with github_repository.repositories,
│   on repositories.tf line 170, in resource "github_repository" "repositories":170:   has_downloads             = true
│ 
│ This attribute is no longer in use, but it hasn't been removed yet. It will
│ be removed in a future version. See
│ https://github.com/orgs/community/discussions/102145#discussioncomment-8351756
│ 
│ (and 26 more similar warnings elsewhere)
╵
─────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so OpenTofu can't
guarantee to take exactly these actions if you run "tofu apply" now.
github/secrets — No changes
github/webhooks — No changes
⚠️ zitadel/cloud — Plan: 10 to add, 2 to change, 0 to destroy.
OpenTofu used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create
  ~ update in-place (current -> planned)
  # onepassword_item.outline_role_sync_zitadel_project_id will be created
  + resource "onepassword_item" "outline_role_sync_zitadel_project_id" {
      + category = "password"
      + id       = (known after apply)
      + password = (sensitive value)
      + title    = "OUTLINE_ROLE_SYNC_ZITADEL_PROJECT_ID"
      + uuid     = (known after apply)
      + vault    = "ilwk2teati3z264z2szn5cwxia"
    }
  # onepassword_item.outline_role_sync_zitadel_token will be created
  + resource "onepassword_item" "outline_role_sync_zitadel_token" {
      + category = "password"
      + id       = (known after apply)
      + password = (sensitive value)
      + title    = "OUTLINE_ROLE_SYNC_ZITADEL_TOKEN"
      + uuid     = (known after apply)
      + vault    = "ilwk2teati3z264z2szn5cwxia"
    }
  # random_password.zitadel_user_initial_password["gitlab-2"] will be created
  + resource "random_password" "zitadel_user_initial_password" {
      + bcrypt_hash = (sensitive value)
      + id          = (known after apply)
      + length      = 64
      + lower       = true
      + min_lower   = 0
      + min_numeric = 0
      + min_special = 0
      + min_upper   = 0
      + number      = true
      + numeric     = true
      + result      = (sensitive value)
      + special     = true
      + upper       = true
    }
  # zitadel_action.map_gitlab_oauth will be created
  + resource "zitadel_action" "map_gitlab_oauth" {
      + allowed_to_fail = true
      + id              = (known after apply)
      + name            = "mapGitLabOAuth"
      + org_id          = "364798781738212816"
      + script          = (known after apply)
      + state           = (known after apply)
      + timeout         = "10s"
    }
  # zitadel_default_login_policy.default will be updated in-place
  ~ resource "zitadel_default_login_policy" "default" {
        id                            = "364772529186811746"
      ~ idps                          = [
          - "364798781687881168",
        ] -> (known after apply)
        # (18 unchanged attributes hidden)
    }
  # zitadel_human_user.users["gitlab-2"] will be created
  + resource "zitadel_human_user" "users" {
      + display_name                 = "eron last_name"
      + email                        = "eron@gitlab.futo.org"
      + first_name                   = "eron"
      + gender                       = "GENDER_UNSPECIFIED"
      + id                           = (known after apply)
      + initial_password             = (sensitive value)
      + initial_skip_password_change = true
      + is_email_verified            = true
      + is_phone_verified            = false
      + last_name                    = "last_name"
      + login_names                  = (known after apply)
      + org_id                       = "364798781738212816"
      + preferred_language           = "und"
      + preferred_login_name         = (known after apply)
      + state                        = (known after apply)
      + user_id                      = (known after apply)
      + user_name                    = "eron"
    }
  # zitadel_idp_gitlab_self_hosted.gitlab will be created
  + resource "zitadel_idp_gitlab_self_hosted" "gitlab" {
      + auto_linking        = "AUTO_LINKING_OPTION_USERNAME"
      + client_id           = "<concealed by 1Password>"
      + client_secret       = (sensitive value)
      + id                  = (known after apply)
      + is_auto_creation    = false
      + is_auto_update      = true
      + is_creation_allowed = false
      + is_linking_allowed  = true
      + issuer              = "<concealed by 1Password>"
      + name                = "FUTO GitLab"
      + scopes              = [
          + "email",
          + "openid",
          + "profile",
        ]
    }
  # zitadel_machine_user.outline_role_sync will be created
  + resource "zitadel_machine_user" "outline_role_sync" {
      + access_token_type    = "ACCESS_TOKEN_TYPE_BEARER"
      + client_id            = (sensitive value)
      + client_secret        = (sensitive value)
      + description          = "Service account for syncing Zitadel roles to Outline groups"
      + id                   = (known after apply)
      + login_names          = (known after apply)
      + name                 = "Outline Role Sync"
      + org_id               = "364798781738212816"
      + preferred_login_name = (known after apply)
      + state                = (known after apply)
      + user_id              = (known after apply)
      + user_name            = "outline-role-sync"
      + with_secret          = false
    }
  # zitadel_org_member.outline_role_sync will be created
  + resource "zitadel_org_member" "outline_role_sync" {
      + id      = (known after apply)
      + org_id  = "364798781738212816"
      + roles   = [
          + "ORG_USER_MANAGER",
        ]
      + user_id = (known after apply)
    }
  # zitadel_personal_access_token.outline_role_sync will be created
  + resource "zitadel_personal_access_token" "outline_role_sync" {
      + expiration_date = "2030-01-01T00:00:00Z"
      + id              = (known after apply)
      + org_id          = "364798781738212816"
      + token           = (sensitive value)
      + user_id         = (known after apply)
    }
  # zitadel_trigger_actions.map_external_oauth will be updated in-place
  # (moved from zitadel_trigger_actions.map_github_oauth)
  ~ resource "zitadel_trigger_actions" "map_external_oauth" {
      ~ action_ids   = [
          - "364798782275214800",
        ] -> (known after apply)
        id           = "364798781738212816_FLOW_TYPE_EXTERNAL_AUTHENTICATION_TRIGGER_TYPE_POST_AUTHENTICATION"
        # (3 unchanged attributes hidden)
    }
  # zitadel_user_metadata.role["gitlab-2"] will be created
  + resource "zitadel_user_metadata" "role" {
      + id      = (known after apply)
      + key     = "role"
      + org_id  = "364798781738212816"
      + user_id = (known after apply)
      + value   = jsonencode(
            [
              + "futo",
            ]
        )
    }
Plan: 10 to add, 2 to change, 0 to destroy.
─────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so OpenTofu can't
guarantee to take exactly these actions if you run "tofu apply" now.
zitadel/self-hosted — No changes

Scoped (dev)

discord/community — No changes
monitoring/grafana — No changes

Scoped (prod)

⚠️ discord/community — Plan: 0 to add, 63 to change, 0 to destroy.
OpenTofu used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  ~ update in-place (current -> planned)
  # discord_forum_channel.dev_focus_topic will be updated in-place
  ~ resource "discord_forum_channel" "dev_focus_topic" {
        id                       = "1045707766754451486"
        name                     = "dev-focus-topic"
      ~ position                 = 22 -> 2
        # (5 unchanged attributes hidden)
    }
  # discord_forum_channel.draft_announcements will be updated in-place
  ~ resource "discord_forum_channel" "draft_announcements" {
        id                       = "1073000522338017381"
        name                     = "draft-announcements"
      ~ position                 = 19 -> 1
        # (5 unchanged attributes hidden)
    }
  # discord_forum_channel.focus_discussion will be updated in-place
  ~ resource "discord_forum_channel" "focus_discussion" {
        id                       = "1026327300284887111"
        name                     = "focus-discussion"
      ~ position                 = 6 -> 2
        # (5 unchanged attributes hidden)
    }
  # discord_forum_channel.help_desk_support will be updated in-place
  ~ resource "discord_forum_channel" "help_desk_support" {
        id                       = "1049703391762321418"
        name                     = "help-desk-support"
      ~ position                 = 4 -> 0
        # (5 unchanged attributes hidden)
    }
  # discord_forum_channel.leadership_focus_topic will be updated in-place
  ~ resource "discord_forum_channel" "leadership_focus_topic" {
        id                       = "1229454284479795291"
        name                     = "leadership-focus-topic"
      ~ position                 = 39 -> 3
        # (5 unchanged attributes hidden)
    }
  # discord_forum_channel.team_focus_topic will be updated in-place
  ~ resource "discord_forum_channel" "team_focus_topic" {
        id                       = "1330248543721754746"
        name                     = "team-focus-topic"
      ~ position                 = 26 -> 3
        # (5 unchanged attributes hidden)
    }
  # discord_forum_channel.team_pull_requests will be updated in-place
  ~ resource "discord_forum_channel" "team_pull_requests" {
        id                       = "1476975523271016661"
        name                     = "team-pull-requests"
      ~ position                 = 27 -> 4
        # (6 unchanged attributes hidden)
    }
  # discord_forum_channel.yucca_focus_topic will be updated in-place
  ~ resource "discord_forum_channel" "yucca_focus_topic" {
        id                       = "1413525610700996739"
        name                     = "yucca-focus-topic"
      ~ position                 = 32 -> 2
        # (6 unchanged attributes hidden)
    }
  # discord_news_channel.github_releases will be updated in-place
  ~ resource "discord_news_channel" "github_releases" {
        id                       = "991477056791658567"
        name                     = "github-releases"
      ~ position                 = 46 -> 5
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.bot_spam will be updated in-place
  ~ resource "discord_text_channel" "bot_spam" {
        id                       = "1159083520027787307"
        name                     = "bot-spam"
      ~ position                 = 47 -> 0
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.branding will be updated in-place
  ~ resource "discord_text_channel" "branding" {
        id                       = "1215143314358009866"
        name                     = "branding"
      ~ position                 = 54 -> 4
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.build_status will be updated in-place
  ~ resource "discord_text_channel" "build_status" {
        id                       = "981216275210584134"
        name                     = "build-status"
      ~ position                 = 66 -> 16
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.cloudflare_status will be updated in-place
  ~ resource "discord_text_channel" "cloudflare_status" {
        id                       = "1313493521755410443"
        name                     = "cloudflare-status"
      ~ position                 = 42 -> 1
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.conference_room will be updated in-place
  ~ resource "discord_text_channel" "conference_room" {
        id                       = "1206801539470069810"
        name                     = "conference-room"
      ~ position                 = 53 -> 3
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.contributing will be updated in-place
  ~ resource "discord_text_channel" "contributing" {
        id                       = "1071165397228855327"
        name                     = "contributing"
      ~ position                 = 7 -> 3
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.dev will be updated in-place
  ~ resource "discord_text_channel" "dev" {
        id                       = "979148343693422593"
        name                     = "dev"
      ~ position                 = 20 -> 0
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.dev_cli will be updated in-place
  ~ resource "discord_text_channel" "dev_cli" {
        id                       = "1175153422866059329"
        name                     = "dev-cli"
      ~ position                 = 64 -> 14
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.dev_fosdem will be updated in-place
  ~ resource "discord_text_channel" "dev_fosdem" {
        id                       = "1291108934861586492"
        name                     = "dev-fosdem"
      ~ position                 = 50 -> 0
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.dev_ml will be updated in-place
  ~ resource "discord_text_channel" "dev_ml" {
        id                       = "1175513104877101078"
        name                     = "dev-ml"
      ~ position                 = 55 -> 5
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.dev_mobile will be updated in-place
  ~ resource "discord_text_channel" "dev_mobile" {
        id                       = "1069026079349669951"
        name                     = "dev-mobile"
      ~ position                 = 58 -> 8
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.dev_off_topic will be updated in-place
  ~ resource "discord_text_channel" "dev_off_topic" {
        id                       = "1034520166115053638"
        name                     = "dev-off-topic"
      ~ position                 = 21 -> 1
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.dev_ops will be updated in-place
  ~ resource "discord_text_channel" "dev_ops" {
        id                       = "1176633064462487686"
        name                     = "dev-ops"
      ~ position                 = 61 -> 11
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.dev_roles will be updated in-place
  ~ resource "discord_text_channel" "dev_roles" {
        id                       = "1196526624812839034"
        name                     = "dev-roles"
      ~ position                 = 59 -> 9
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.dev_security will be updated in-place
  ~ resource "discord_text_channel" "dev_security" {
        id                       = "1020479445167001650"
        name                     = "dev-security"
      ~ position                 = 60 -> 10
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.dev_server will be updated in-place
  ~ resource "discord_text_channel" "dev_server" {
        id                       = "1155894065062236160"
        name                     = "dev-server"
      ~ position                 = 52 -> 2
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.dev_web will be updated in-place
  ~ resource "discord_text_channel" "dev_web" {
        id                       = "1169697266806820974"
        name                     = "dev-web"
      ~ position                 = 63 -> 13
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.developer_updates will be updated in-place
  ~ resource "discord_text_channel" "developer_updates" {
        id                       = "1361300925788192778"
        name                     = "developer-updates"
      ~ position                 = 41 -> 0
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.emotes will be updated in-place
  ~ resource "discord_text_channel" "emotes" {
        id                       = "1287169306244943894"
        name                     = "emotes"
      ~ position                 = 48 -> 1
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.fosdem_2025 will be updated in-place
  ~ resource "discord_text_channel" "fosdem_2025" {
        id                       = "1334949885442654248"
        name                     = "fosdem-2025"
      ~ position                 = 51 -> 1
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.futo will be updated in-place
  ~ resource "discord_text_channel" "futo" {
        id                       = "1208144146825748491"
        name                     = "futo"
      ~ position                 = 56 -> 6
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.futo_discussion_old will be updated in-place
  ~ resource "discord_text_channel" "futo_discussion_old" {
        id                       = "1238236317729554523"
        name                     = "futo-discussion-old"
      ~ position                 = 62 -> 12
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.github_issues_and_discussion will be updated in-place
  ~ resource "discord_text_channel" "github_issues_and_discussion" {
        id                       = "991483015958106202"
        name                     = "github-issues-and-discussion"
      ~ position                 = 44 -> 3
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.github_pull_requests will be updated in-place
  ~ resource "discord_text_channel" "github_pull_requests" {
        id                       = "991483093179445350"
        name                     = "github-pull-requests"
      ~ position                 = 45 -> 4
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.github_status will be updated in-place
  ~ resource "discord_text_channel" "github_status" {
        id                       = "1240662502912692236"
        name                     = "github-status"
      ~ position                 = 43 -> 2
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.imagegenius_aio will be updated in-place
  ~ resource "discord_text_channel" "imagegenius_aio" {
        id                       = "1178366211675930634"
        name                     = "imagegenius-aio"
      ~ position                 = 11 -> 0
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.immich will be updated in-place
  ~ resource "discord_text_channel" "immich" {
        id                       = "994044917355663450"
        name                     = "immich"
      ~ position                 = 9 -> 5
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.immich_frame will be updated in-place
  ~ resource "discord_text_channel" "immich_frame" {
        id                       = "1217843270244372480"
        name                     = "immich-frame"
      ~ position                 = 13 -> 2
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.immich_go will be updated in-place
  ~ resource "discord_text_channel" "immich_go" {
        id                       = "1178366369423700080"
        name                     = "immich-go"
      ~ position                 = 12 -> 1
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.immich_kiosk will be updated in-place
  ~ resource "discord_text_channel" "immich_kiosk" {
        id                       = "1293191523927851099"
        name                     = "immich-kiosk"
      ~ position                 = 14 -> 3
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.immich_nix will be updated in-place
  ~ resource "discord_text_channel" "immich_nix" {
        id                       = "1288786177952059435"
        name                     = "immich-nix"
      ~ position                 = 57 -> 7
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.immich_power_tools will be updated in-place
  ~ resource "discord_text_channel" "immich_power_tools" {
        id                       = "1278195895594258553"
        name                     = "immich-power-tools"
      ~ position                 = 15 -> 4
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.jasons_adventures_with_unraid_docker_and_networking will be updated in-place
  ~ resource "discord_text_channel" "jasons_adventures_with_unraid_docker_and_networking" {
        id                       = "1230989262325813270"
        name                     = "jasons-adventures-with-unraid-docker-and-networking"
      ~ position                 = 65 -> 15
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.leadership will be updated in-place
  ~ resource "discord_text_channel" "leadership" {
        id                       = "1176686059422232636"
        name                     = "leadership"
      ~ position                 = 36 -> 0
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.leadership_alerts will be updated in-place
  ~ resource "discord_text_channel" "leadership_alerts" {
        id                       = "1260975801625608213"
        name                     = "leadership-alerts"
      ~ position                 = 38 -> 2
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.leadership_off_topic will be updated in-place
  ~ resource "discord_text_channel" "leadership_off_topic" {
        id                       = "1255863786686906489"
        name                     = "leadership-off-topic"
      ~ position                 = 37 -> 1
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.merch will be updated in-place
  ~ resource "discord_text_channel" "merch" {
        id                       = "1336794023888818288"
        name                     = "merch"
      ~ position                 = 10 -> 6
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.moderator_only will be updated in-place
  ~ resource "discord_text_channel" "moderator_only" {
        id                       = "991930223991988255"
        name                     = "moderator-only"
      ~ position                 = 40 -> 4
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.off_topic will be updated in-place
  ~ resource "discord_text_channel" "off_topic" {
        id                       = "991643870523830382"
        name                     = "off-topic"
      ~ position                 = 49 -> 2
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.package_maintainers will be updated in-place
  ~ resource "discord_text_channel" "package_maintainers" {
        id                       = "1288859036015398974"
        name                     = "package-maintainers"
      ~ position                 = 5 -> 1
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.support_crew will be updated in-place
  ~ resource "discord_text_channel" "support_crew" {
        id                       = "1184258493948117084"
        name                     = "support-crew"
      ~ position                 = 18 -> 0
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.team will be updated in-place
  ~ resource "discord_text_channel" "team" {
        id                       = "1330248080271999086"
        name                     = "team"
      ~ position                 = 23 -> 0
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.team_alerts will be updated in-place
  ~ resource "discord_text_channel" "team_alerts" {
        id                       = "1360190417643110460"
        name                     = "team-alerts"
      ~ position                 = 29 -> 6
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.team_off_topic will be updated in-place
  ~ resource "discord_text_channel" "team_off_topic" {
        id                       = "1330252547751022632"
        name                     = "team-off-topic"
      ~ position                 = 25 -> 2
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.team_purchases will be updated in-place
  ~ resource "discord_text_channel" "team_purchases" {
        id                       = "1263492970691297300"
        name                     = "team-purchases"
      ~ position                 = 28 -> 5
        # (5 unchanged attributes hidden)
    }
  # discord_text_channel.team_slop will be updated in-place
  ~ resource "discord_text_channel" "team_slop" {
        id                       = "1485644163247640657"
        name                     = "team-slop"
      ~ position                 = 24 -> 1
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.translations will be updated in-place
  ~ resource "discord_text_channel" "translations" {
        id                       = "1250427404976132138"
        name                     = "translations"
      ~ position                 = 8 -> 4
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.truenas will be updated in-place
  ~ resource "discord_text_channel" "truenas" {
        id                       = "1178410588821524561"
        name                     = "truenas"
      ~ position                 = 16 -> 5
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.unraid will be updated in-place
  ~ resource "discord_text_channel" "unraid" {
        id                       = "1228387901889445989"
        name                     = "unraid"
      ~ position                 = 17 -> 6
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.yucca will be updated in-place
  ~ resource "discord_text_channel" "yucca" {
        id                       = "1413525175747608688"
        name                     = "yucca"
      ~ position                 = 30 -> 0
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.yucca_alerts will be updated in-place
  ~ resource "discord_text_channel" "yucca_alerts" {
        id                       = "1413525667429093469"
        name                     = "yucca-alerts"
      ~ position                 = 33 -> 3
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.yucca_alerts_dev will be updated in-place
  ~ resource "discord_text_channel" "yucca_alerts_dev" {
        id                       = "1425822463736021134"
        name                     = "yucca-alerts-dev"
      ~ position                 = 35 -> 5
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.yucca_alerts_testing will be updated in-place
  ~ resource "discord_text_channel" "yucca_alerts_testing" {
        id                       = "1425822461890662460"
        name                     = "yucca-alerts-testing"
      ~ position                 = 34 -> 4
        # (6 unchanged attributes hidden)
    }
  # discord_text_channel.yucca_off_topic will be updated in-place
  ~ resource "discord_text_channel" "yucca_off_topic" {
        id                       = "1413525269192376413"
        name                     = "yucca-off-topic"
      ~ position                 = 31 -> 1
        # (6 unchanged attributes hidden)
    }
Plan: 0 to add, 63 to change, 0 to destroy.
─────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so OpenTofu can't
guarantee to take exactly these actions if you run "tofu apply" now.
monitoring/grafana — No changes

@zackpollard zackpollard marked this pull request as ready for review March 30, 2026 15:06
@zackpollard zackpollard requested a review from a team as a code owner March 30, 2026 15:06
@zackpollard zackpollard force-pushed the feat/gitlab-oauth-zitadel branch from da4eec2 to b1bd88d Compare March 30, 2026 17:40
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 30, 2026

--- HelmRelease: tools/outline-role-sync Service: tools/outline-role-sync

+++ HelmRelease: tools/outline-role-sync Service: tools/outline-role-sync

@@ -0,0 +1,23 @@

+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: outline-role-sync
+  labels:
+    app.kubernetes.io/instance: outline-role-sync
+    app.kubernetes.io/managed-by: Helm
+    app.kubernetes.io/name: outline-role-sync
+    app.kubernetes.io/service: outline-role-sync
+  namespace: tools
+spec:
+  type: ClusterIP
+  ports:
+  - port: 8080
+    targetPort: 8080
+    protocol: TCP
+    name: http
+  selector:
+    app.kubernetes.io/controller: outline-role-sync
+    app.kubernetes.io/instance: outline-role-sync
+    app.kubernetes.io/name: outline-role-sync
+
--- HelmRelease: tools/outline-role-sync Deployment: tools/outline-role-sync

+++ HelmRelease: tools/outline-role-sync Deployment: tools/outline-role-sync

@@ -0,0 +1,60 @@

+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: outline-role-sync
+  labels:
+    app.kubernetes.io/controller: outline-role-sync
+    app.kubernetes.io/instance: outline-role-sync
+    app.kubernetes.io/managed-by: Helm
+    app.kubernetes.io/name: outline-role-sync
+  namespace: tools
+spec:
+  revisionHistoryLimit: 3
+  replicas: 1
+  strategy:
+    type: Recreate
+  selector:
+    matchLabels:
+      app.kubernetes.io/controller: outline-role-sync
+      app.kubernetes.io/name: outline-role-sync
+      app.kubernetes.io/instance: outline-role-sync
+  template:
+    metadata:
+      labels:
+        app.kubernetes.io/controller: outline-role-sync
+        app.kubernetes.io/instance: outline-role-sync
+        app.kubernetes.io/name: outline-role-sync
+        podbump.bo0tzz.me/enabled: 'true'
+    spec:
+      enableServiceLinks: false
+      serviceAccountName: default
+      automountServiceAccountToken: true
+      hostIPC: false
+      hostNetwork: false
+      hostPID: false
+      dnsPolicy: ClusterFirst
+      containers:
+      - env:
+        - name: OUTLINE_BASE_URL
+          value: https://outline.immich.cloud
+        - name: PORT
+          value: '8080'
+        - name: ZITADEL_BASE_URL
+          value: https://zitadel.internal.immich.cloud
+        envFrom:
+        - secretRef:
+            name: outline-role-sync
+        image: ghcr.io/immich-app/outline-role-sync:release
+        livenessProbe:
+          httpGet:
+            path: /health
+            port: 8080
+          periodSeconds: 30
+        name: app
+        readinessProbe:
+          httpGet:
+            path: /health
+            port: 8080
+          periodSeconds: 10
+

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 30, 2026

Terraform Plan

Shared

1password/account — No changes
1password/futo-account — No changes
cloudflare/account — No changes
cloudflare/api-keys — No changes
docker/org — No changes
github/org — No changes
github/secrets — No changes
github/webhooks — No changes
⚠️ zitadel/cloud — Plan: 10 to add, 2 to change, 0 to destroy.
OpenTofu used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create
  ~ update in-place (current -> planned)
  # onepassword_item.outline_role_sync_zitadel_project_id will be created
  + resource "onepassword_item" "outline_role_sync_zitadel_project_id" {
      + category = "password"
      + id       = (known after apply)
      + password = (sensitive value)
      + title    = "OUTLINE_ROLE_SYNC_ZITADEL_PROJECT_ID"
      + uuid     = (known after apply)
      + vault    = "ilwk2teati3z264z2szn5cwxia"
    }
  # onepassword_item.outline_role_sync_zitadel_token will be created
  + resource "onepassword_item" "outline_role_sync_zitadel_token" {
      + category = "password"
      + id       = (known after apply)
      + password = (sensitive value)
      + title    = "OUTLINE_ROLE_SYNC_ZITADEL_TOKEN"
      + uuid     = (known after apply)
      + vault    = "ilwk2teati3z264z2szn5cwxia"
    }
  # random_password.zitadel_user_initial_password["gitlab-2"] will be created
  + resource "random_password" "zitadel_user_initial_password" {
      + bcrypt_hash = (sensitive value)
      + id          = (known after apply)
      + length      = 64
      + lower       = true
      + min_lower   = 0
      + min_numeric = 0
      + min_special = 0
      + min_upper   = 0
      + number      = true
      + numeric     = true
      + result      = (sensitive value)
      + special     = true
      + upper       = true
    }
  # zitadel_action.map_gitlab_oauth will be created
  + resource "zitadel_action" "map_gitlab_oauth" {
      + allowed_to_fail = true
      + id              = (known after apply)
      + name            = "mapGitLabOAuth"
      + org_id          = "364798781738212816"
      + script          = (known after apply)
      + state           = (known after apply)
      + timeout         = "10s"
    }
  # zitadel_default_login_policy.default will be updated in-place
  ~ resource "zitadel_default_login_policy" "default" {
        id                            = "364772529186811746"
      ~ idps                          = [
          - "364798781687881168",
        ] -> (known after apply)
        # (18 unchanged attributes hidden)
    }
  # zitadel_human_user.users["gitlab-2"] will be created
  + resource "zitadel_human_user" "users" {
      + display_name                 = "eron last_name"
      + email                        = "eron@gitlab.futo.org"
      + first_name                   = "eron"
      + gender                       = "GENDER_UNSPECIFIED"
      + id                           = (known after apply)
      + initial_password             = (sensitive value)
      + initial_skip_password_change = true
      + is_email_verified            = true
      + is_phone_verified            = false
      + last_name                    = "last_name"
      + login_names                  = (known after apply)
      + org_id                       = "364798781738212816"
      + preferred_language           = "und"
      + preferred_login_name         = (known after apply)
      + state                        = (known after apply)
      + user_id                      = (known after apply)
      + user_name                    = "eron"
    }
  # zitadel_idp_gitlab_self_hosted.gitlab will be created
  + resource "zitadel_idp_gitlab_self_hosted" "gitlab" {
      + auto_linking        = "AUTO_LINKING_OPTION_USERNAME"
      + client_id           = "<concealed by 1Password>"
      + client_secret       = (sensitive value)
      + id                  = (known after apply)
      + is_auto_creation    = false
      + is_auto_update      = true
      + is_creation_allowed = false
      + is_linking_allowed  = true
      + issuer              = "<concealed by 1Password>"
      + name                = "FUTO GitLab"
      + scopes              = [
          + "email",
          + "openid",
          + "profile",
        ]
    }
  # zitadel_machine_user.outline_role_sync will be created
  + resource "zitadel_machine_user" "outline_role_sync" {
      + access_token_type    = "ACCESS_TOKEN_TYPE_BEARER"
      + client_id            = (sensitive value)
      + client_secret        = (sensitive value)
      + description          = "Service account for syncing Zitadel roles to Outline groups"
      + id                   = (known after apply)
      + login_names          = (known after apply)
      + name                 = "Outline Role Sync"
      + org_id               = "364798781738212816"
      + preferred_login_name = (known after apply)
      + state                = (known after apply)
      + user_id              = (known after apply)
      + user_name            = "outline-role-sync"
      + with_secret          = false
    }
  # zitadel_org_member.outline_role_sync will be created
  + resource "zitadel_org_member" "outline_role_sync" {
      + id      = (known after apply)
      + org_id  = "364798781738212816"
      + roles   = [
          + "ORG_USER_MANAGER",
        ]
      + user_id = (known after apply)
    }
  # zitadel_personal_access_token.outline_role_sync will be created
  + resource "zitadel_personal_access_token" "outline_role_sync" {
      + expiration_date = "2030-01-01T00:00:00Z"
      + id              = (known after apply)
      + org_id          = "364798781738212816"
      + token           = (sensitive value)
      + user_id         = (known after apply)
    }
  # zitadel_trigger_actions.map_external_oauth will be updated in-place
  # (moved from zitadel_trigger_actions.map_github_oauth)
  ~ resource "zitadel_trigger_actions" "map_external_oauth" {
      ~ action_ids   = [
          - "364798782275214800",
        ] -> (known after apply)
        id           = "364798781738212816_FLOW_TYPE_EXTERNAL_AUTHENTICATION_TRIGGER_TYPE_POST_AUTHENTICATION"
        # (3 unchanged attributes hidden)
    }
  # zitadel_user_metadata.role["gitlab-2"] will be created
  + resource "zitadel_user_metadata" "role" {
      + id      = (known after apply)
      + key     = "role"
      + org_id  = "364798781738212816"
      + user_id = (known after apply)
      + value   = jsonencode(
            [
              + "futo",
            ]
        )
    }
Plan: 10 to add, 2 to change, 0 to destroy.
─────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so OpenTofu can't
guarantee to take exactly these actions if you run "tofu apply" now.
zitadel/self-hosted — No changes

Scoped (dev)

discord/community — No changes
monitoring/grafana — No changes

Scoped (prod)

discord/community — No changes
monitoring/grafana — No changes

@zackpollard zackpollard force-pushed the feat/gitlab-oauth-zitadel branch 2 times, most recently from 6b0087e to 6d2208f Compare March 31, 2026 02:24
@zackpollard zackpollard force-pushed the feat/gitlab-oauth-zitadel branch from 6d2208f to e44474c Compare March 31, 2026 16:10
@zackpollard zackpollard merged commit 5d02b07 into main Mar 31, 2026
19 checks passed
@zackpollard zackpollard deleted the feat/gitlab-oauth-zitadel branch March 31, 2026 16:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants