Crear un repositorio en GitHub es fácil.

Mantener cientos de repositorios, equipos, permisos, rulesets, entornos, secretos y variables consistentes a lo largo del tiempo no lo es.

La configuración inicial rara vez es el verdadero problema.

El problema aparece después:

  • un repositorio permite merge commits mientras otro solo admite squash merges
  • las alertas de vulnerabilidades están activadas en unos repositorios y desactivadas en otros
  • un equipo conserva acceso a repositorios que ya no le pertenecen
  • un usuario se añade manualmente y nunca se elimina
  • alguien modifica un ruleset directamente desde la interfaz de GitHub
  • un repositorio nuevo no incluye la configuración estándar de la organización
  • nadie sabe si el estado correcto está en la interfaz, en un script, en Terraform o en un runbook antiguo

En ese momento, GitHub deja de ser simplemente el lugar donde se almacena el código fuente.

Pasa a formar parte de la plataforma de ingeniería.

Ese es el problema que quise abordar con github-platform-operator.

github-platform-operator es un operador open source de Kubernetes para crear, adoptar y reconciliar continuamente recursos habituales de una plataforma GitHub de forma declarativa.

Gestiona:

repositorios
rulesets de repositorio
miembros de la organización
equipos
membresías de equipos
accesos a repositorios
entornos
secretos de Actions
variables de Actions

La configuración deseada vive en recursos de Kubernetes y puede desplegarse mediante el mismo flujo GitOps utilizado para el resto de la plataforma.

La configuración de GitHub es estado de plataforma, y el estado de plataforma debería ser declarativo, revisable, adoptable y reconciliado continuamente.


La configuración de GitHub también sufre drift

Normalmente hablamos de drift de infraestructura al referirnos a recursos cloud, workloads de Kubernetes, redes o IAM.

Pero la configuración de GitHub sufre exactamente el mismo problema.

Un repositorio puede comenzar con la configuración correcta:

visibilidad: privada
eliminar ramas después del merge: activado
alertas de vulnerabilidades: activadas
squash merge: activado
aprobaciones requeridas: 1
revisión de CODEOWNERS: obligatoria

Después alguien modifica una opción directamente desde la interfaz.

El repositorio sigue existiendo. Las aplicaciones siguen compilando. Nada falla de forma inmediata.

Pero la plataforma ya no se encuentra en el estado esperado.

El cambio puede pasar desapercibido hasta que:

  • una pull request se fusiona mediante una estrategia no permitida
  • se omite una revisión obligatoria
  • un usuario antiguo conserva acceso
  • se descubre que un control de seguridad estaba desactivado
  • un repositorio nuevo se comporta de forma distinta al resto

No es muy diferente de editar manualmente una regla de firewall en cloud o modificar un Deployment de Kubernetes fuera de GitOps.

El estado ha sufrido drift.

La diferencia es que GitHub suele tratarse como una herramienta administrativa y no como parte del control plane de la plataforma.

Esa distinción resulta difícil de defender cuando GitHub controla:

  • quién puede acceder al código fuente
  • cómo llega el código a producción
  • qué revisiones son obligatorias
  • qué entornos pueden desplegar
  • qué secretos están disponibles para las automatizaciones
  • qué equipos son propietarios de cada repositorio
  • cómo se crean y retiran los repositorios

Para muchas organizaciones de ingeniería, la configuración de GitHub es infraestructura.


Por qué los scripts dejan de escalar

Un script suele ser la forma más rápida de automatizar el primer repositorio.

Por ejemplo:

gh api \
  --method PATCH \
  /repos/example-org/payments-api \
  -f delete_branch_on_merge=true \
  -f allow_squash_merge=true

Otro script puede configurar el acceso de un equipo:

gh api \
  --method PUT \
  /orgs/example-org/teams/platform/repos/example-org/payments-api \
  -f permission=maintain

Los scripts son útiles.

También son la herramienta adecuada para importaciones, migraciones, mantenimiento puntual y tareas de descubrimiento.

El problema aparece cuando se convierten en el mecanismo permanente de control.

Un script describe una secuencia de acciones:

buscar el repositorio
      ↓
crearlo si no existe
      ↓
actualizar la configuración
      ↓
configurar los equipos
      ↓
crear el ruleset
      ↓
continuar si todas las operaciones anteriores tuvieron éxito

Pero una plataforma normalmente necesita describir el resultado deseado:

Este repositorio debe existir.
Esta configuración debe mantenerse.
Este equipo debe tener este permiso.
Este ruleset debe permanecer activo.

Son modelos distintos.

Un script debe decidir:

  • qué recursos existen ya
  • qué operaciones se completaron correctamente
  • cómo continuar después de un fallo parcial
  • cómo detectar cambios manuales posteriores
  • cómo gestionar dependencias
  • cómo informar del estado observado
  • cuándo reintentar
  • cómo evitar reintentos durante un rate limit de la API
  • qué debe ocurrir cuando se elimina la configuración

Con el tiempo, el script empieza a implementar un bucle de reconciliación.

En ese punto se está convirtiendo en un controlador, pero sin la API de Kubernetes, el modelo de estado, los watches, los finalizers, los eventos y la semántica de ciclo de vida que ya ofrece Kubernetes.

Un script describe acciones. Un recurso de Kubernetes describe el estado deseado.


Una breve introducción a los operadores de Kubernetes

Un operador de Kubernetes amplía la API de Kubernetes con nuevos tipos de recursos y controladores.

El recurso describe el estado deseado:

apiVersion: github.k8sready.com/v1alpha1
kind: GitHubRepository
metadata:
  name: payments-api
spec:
  providerConfigRef: default
  name: payments-api
  visibility: private
  deleteBranchOnMerge: true
  vulnerabilityAlerts: true
  deletionPolicy: Orphan

Un controlador observa ese recurso y lo reconcilia con el sistema externo.

En este caso:

GitHubRepository
       ↓
controlador de Kubernetes
       ↓
API REST de GitHub
       ↓
repositorio observado en GitHub
       ↓
estado en Kubernetes

El controlador compara repetidamente el estado deseado con el estado observado.

Si el repositorio no existe, puede crearlo.

Si ya existe, puede adoptarlo.

Si una configuración gestionada cambia de forma remota, el controlador puede restaurar el valor declarado.

Si GitHub no está disponible temporalmente, el controlador puede reencolar la operación.

Si el rate limit de la API se agota, el controlador puede esperar hasta el reset en lugar de generar un bucle de reintentos descontrolado.

Lo importante no es que Kubernetes pueda llamar a una API externa.

Lo importante es que el ciclo de vida pasa a ser declarativo.

Estado deseado
      ↓
Reconciliar
      ↓
Observar
      ↓
Corregir el drift
      ↓
Informar del estado
      ↺

La idea detrás de github-platform-operator

El objetivo de github-platform-operator no es exponer cada endpoint de la API REST de GitHub como un recurso personalizado.

Se centra deliberadamente en flujos recurrentes de plataforma:

GitHubProviderConfig
GitHubRepository
GitHubRepositoryRuleset
GitHubOrganizationMember
GitHubTeam
GitHubTeamMembership
GitHubRepositoryTeamAccess
GitHubRepositoryCollaborator
GitHubEnvironment
GitHubActionsSecret
GitHubActionsVariable

Esto permite que un equipo de plataforma describa un repositorio y sus dependencias habituales mediante manifiestos de Kubernetes.

Por ejemplo:

apiVersion: github.k8sready.com/v1alpha1
kind: GitHubRepository
metadata:
  name: payments-api
  namespace: platform
spec:
  providerConfigRef: default
  name: payments-api
  visibility: private
  description: Payments service
  topics:
    - golang
    - kubernetes
    - payments
  features:
    issues: true
    projects: false
    wiki: false
    discussions: true
  mergeOptions:
    allowMergeCommit: false
    allowRebaseMerge: false
    allowSquashMerge: true
  deleteBranchOnMerge: true
  vulnerabilityAlerts: true
  deletionPolicy: Orphan

El manifiesto puede revisarse como cualquier otro cambio de plataforma:

el desarrollador abre una pull request
            ↓
revisión del equipo de plataforma
            ↓
merge
            ↓
Argo CD aplica el recurso
            ↓
el operador reconcilia GitHub

El repositorio Git se convierte en la fuente del estado deseado.

Kubernetes proporciona la API y el ciclo de vida.

El operador traduce ese estado deseado en operaciones sobre GitHub.

github-platform-operator architecture: GitOps manifests flow through Kubernetes to continuously reconcile GitHub resources
github-platform-operator turns GitOps-managed Kubernetes resources into continuously reconciled GitHub configuration.

El caso real no parte de una organización vacía

Crear un repositorio nuevo es sencillo.

Adoptar una organización existente es más difícil.

Las migraciones reales de plataforma rara vez comienzan con:

0 repositorios
0 equipos
0 usuarios
0 convenciones existentes

Empiezan con años de estado acumulado:

  • repositorios creados por personas diferentes
  • equipos con miembros existentes
  • colaboradores directos
  • configuraciones de merge inconsistentes
  • rulesets ya creados
  • entornos y configuración de Actions
  • ownership mantenido manualmente
  • automatizaciones parciales de proyectos anteriores

Un operador útil debe soportar la adopción de entornos brownfield.

Por eso github-platform-operator no presupone que todos los recursos remotos deban crearse desde cero.

Si ya existe un repositorio con el nombre declarado, el controlador puede adoptarlo.

Si ya existe un equipo, puede adoptarlo y reconciliarlo.

El mismo principio se aplica al resto de recursos compatibles.

También por eso importan los campos omitidos.

Para la configuración de repositorios:

campo omitido         → observarlo, pero no gestionarlo
campo configurado     → reconciliarlo continuamente
cadena vacía          → borrarlo explícitamente
lista vacía           → eliminar explícitamente todos los elementos gestionados

Esto evita un problema habitual en las migraciones: adoptar un recurso no debería obligar al operador a hacerse cargo de toda su configuración remota desde el primer momento.

Un equipo puede comenzar gestionando solo unos pocos campos e ir ampliando ese ownership de forma gradual.

Las plataformas reales rara vez comienzan con una organización vacía. La adopción no es un caso límite; es el camino de migración.


Importar equipos existentes a GitOps

La adopción resulta todavía más útil cuando el estado remoto actual puede convertirse en manifiestos.

Por ese motivo también creé un flujo de importación que:

lista los equipos existentes de la organización
      ↓
consulta sus membresías directas
      ↓
conserva los roles member y maintainer
      ↓
genera manifiestos GitHubTeam
      ↓
genera manifiestos GitHubTeamMembership
      ↓
construye la estructura de Kustomize

Un equipo generado puede tener este aspecto:

apiVersion: github.k8sready.com/v1alpha1
kind: GitHubTeam
metadata:
  name: platform
  namespace: github-platform-operator-system
spec:
  providerConfigRef: default
  name: Platform
  privacy: closed
  deletionPolicy: Orphan

Sus miembros directos actuales se convierten en recursos individuales:

apiVersion: github.k8sready.com/v1alpha1
kind: GitHubTeamMembership
metadata:
  name: platform-octocat
  namespace: github-platform-operator-system
spec:
  teamRef:
    name: platform
  username: octocat
  role: member
  deletionPolicy: Orphan

El importador no necesita volver a crear los equipos de forma remota.

Genera el estado deseado necesario para que el operador pueda adoptarlos.

La importación es una operación de migración.

La reconciliación es una responsabilidad continua del control plane.


Las APIs legibles para humanos importan

Las APIs externas suelen exponer identificadores internos.

Los actores de bypass de los rulesets de GitHub son un buen ejemplo.

Un ruleset puede necesitar un ID numérico para un equipo o un usuario, pero esos IDs son incómodos en GitOps:

bypassActors:
  - actorType: Team
    actorID: 1234567

Ese valor resulta difícil de comprender durante una revisión y difícil de mantener.

La API del operador permite utilizar identificadores legibles:

bypassActors:
  - actorType: Team
    teamSlug: platform
    bypassMode: always
  - actorType: User
    username: release-admin
    bypassMode: pull_request

Durante la reconciliación, el controlador resuelve el slug del equipo o el nombre de usuario al identificador numérico que GitHub necesita.

Así, los detalles internos de la implementación remota no se filtran en la API de estado deseado.

Una API declarativa debería exponer identificadores estables y legibles siempre que sea posible, en lugar de filtrar detalles internos del sistema remoto.

Estas decisiones determinan si una API seguirá siendo comprensible después de cientos de manifiestos y años de cambios.


Los rulesets como política de plataforma

Un repositorio no está listo simplemente porque exista.

También necesita convenciones de trabajo.

Un ruleset para la rama por defecto puede representarse así:

apiVersion: github.k8sready.com/v1alpha1
kind: GitHubRepositoryRuleset
metadata:
  name: payments-api-default-branch
  namespace: platform
spec:
  repositoryRef:
    name: payments-api
  name: default-branch-protection
  target: branch
  enforcement: active
  bypassActors:
    - actorType: Team
      teamSlug: platform
      bypassMode: always
  conditions:
    refName:
      include:
        - "~DEFAULT_BRANCH"
      exclude: []
  rules:
    - type: deletion
    - type: non_fast_forward
    - type: pull_request
      parameters:
        required_approving_review_count: 1
        require_code_owner_review: true
        allowed_merge_methods:
          - squash
  deletionPolicy: Orphan

Esto convierte la política del repositorio en algo:

  • revisable mediante una pull request
  • reproducible entre repositorios
  • visible dentro del mismo repositorio de plataforma
  • reconciliado continuamente
  • independiente de la configuración manual desde la interfaz

También permite generar políticas consistentes para familias de repositorios como:

gitops-*
infra-terraform-*

Seguro por defecto

La automatización declarativa puede ser peligrosa cuando la semántica de eliminación es implícita.

Eliminar un objeto de Kubernetes no debería destruir de forma inesperada un repositorio de producción.

github-platform-operator utiliza políticas de eliminación explícitas:

Orphan
Archive
Delete
Revoke

Para un repositorio:

deletionPolicy: Orphan

significa que eliminar el recurso de Kubernetes conserva el repositorio en GitHub.

deletionPolicy: Archive

lo archiva.

deletionPolicy: Delete

lo elimina permanentemente.

Para los recursos de acceso, Revoke elimina explícitamente la membresía o el permiso remoto.

El valor seguro por defecto es Orphan.

El operador también utiliza finalizers cuando la limpieza remota debe completarse antes de que Kubernetes elimine el recurso.

También tiene en cuenta las dependencias.

Por ejemplo, un equipo no debería eliminarse mientras existan membresías gestionadas que lo referencien.

Un entorno configurado para borrarse debería permanecer protegido mientras existan recursos de Actions que dependan de él.

Estos comportamientos no son detalles de implementación.

Forman parte del contrato de la API.

La infraestructura declarativa no debería convertirse en infraestructura destructiva por accidente.


Suspender un provider completo

En ocasiones es necesario detener la reconciliación externa.

Algunos motivos pueden ser:

  • rotación de credenciales
  • mantenimiento de GitHub
  • un incidente relacionado con el uso de la API
  • un consumidor inesperado del rate limit
  • una migración planificada
  • una investigación de drift remoto
  • trabajos administrativos temporales desde la interfaz de GitHub

El provider puede suspenderse de forma declarativa:

apiVersion: github.k8sready.com/v1alpha1
kind: GitHubProviderConfig
metadata:
  name: default
spec:
  organization: example-org
  suspended: true
  credentials:
    secretRef:
      namespace: github-platform-operator-system
      name: github-credentials
      key: token

Mientras está suspendido:

los controladores detienen la reconciliación remota
no se leen las credenciales
se detienen las llamadas a la API de GitHub
los recursos gestionados informan ReconciliationSuspended
los recursos de Kubernetes permanecen presentes

Esto ofrece un interruptor de emergencia del control plane sin escalar el controlador a cero ni eliminar los recursos gestionados.

Como la suspensión forma parte del recurso del provider, también puede gestionarse mediante GitOps.


Los rate limits forman parte del diseño de un controlador

GitHub es una API externa con límites primarios y secundarios.

Un controlador que ignore esos límites puede convertir un fallo temporal en una tormenta continua de errores.

El problema es mayor cuando varios reconciliadores comparten las mismas credenciales.

Repositorios, equipos, membresías, rulesets, entornos, secretos y variables pueden consumir el mismo presupuesto de API.

Por eso el operador utiliza una puerta compartida y reactiva para los rate limits.

Cuando GitHub informa de un límite, el operador puede:

leer Retry-After
o leer el timestamp de reset
      ↓
pausar las peticiones remotas mediante la puerta compartida
      ↓
reencolar los recursos afectados
      ↓
reanudar cuando el límite lo permita

Esto es diferente de añadir simplemente un retraso a un controlador concreto.

El presupuesto de la API pertenece a la credencial o a la instalación, por lo que la coordinación debe producirse entre todos los reconciliadores que utilicen esa identidad.

Gestionarlo correctamente forma parte de operar un control plane externo; no es una optimización opcional.


Secretos sin valores en texto plano dentro de los custom resources

Los secretos de GitHub Actions también se representan de forma declarativa, pero el valor en texto plano no debería almacenarse en el custom resource.

En su lugar, el recurso referencia un Secret de Kubernetes:

apiVersion: github.k8sready.com/v1alpha1
kind: GitHubActionsSecret
metadata:
  name: payments-api-docker-token
  namespace: platform
spec:
  target:
    repositoryRef:
      name: payments-api
  name: DOCKER_TOKEN
  valueFrom:
    secretKeyRef:
      name: payments-actions-values
      key: docker-token
  deletionPolicy: Orphan

El controlador:

lee el Secret de Kubernetes referenciado
      ↓
obtiene la clave pública de GitHub
      ↓
cifra el valor
      ↓
envía el payload cifrado
      ↓
no almacena el texto plano en status

Los cambios en el Secret de Kubernetes disparan una reconciliación y rotan el valor remoto.

El objetivo no es únicamente automatizar la llamada a la API.

El objetivo es ofrecer una API que favorezca un uso más seguro.


¿Por qué no Terraform?

Terraform dispone de un provider maduro para GitHub y es una opción sólida para gestionar sus recursos.

Resulta especialmente adecuado cuando:

  • la configuración de GitHub ya forma parte de una plataforma Terraform existente
  • los equipos prefieren cambios basados en plan y apply
  • el aprovisionamiento de infraestructura está centralizado en Terraform
  • una reconciliación periódica es suficiente
  • Kubernetes no actúa como control plane de la plataforma

github-platform-operator está diseñado para un modelo operativo distinto.

Encaja en entornos donde:

  • Kubernetes ya proporciona la API del control plane
  • Argo CD o Flux ya despliegan el estado deseado
  • los recursos de plataforma deben exponer status de Kubernetes
  • los controladores deben corregir el drift de forma continua
  • los recursos de GitHub deben componerse con referencias de Kubernetes
  • la suspensión y el comportamiento de los finalizers deben formar parte de la API

La diferencia no es que una herramienta pueda crear un repositorio y la otra no.

La diferencia es el modelo de ciclo de vida.

Terraform:
configuración → plan → apply

Operador:
estado deseado → observar → reconciliar → repetir

Ninguno de los dos modelos es universalmente mejor.

Están optimizados para arquitecturas de plataforma diferentes.


¿Por qué no Crossplane?

Crossplane también permite gestionar servicios externos mediante Kubernetes, incluido GitHub a través de un provider.

Para organizaciones que ya utilizan Crossplane como control plane de plataforma, puede ser la opción natural.

Crossplane ofrece capacidades que van mucho más allá de GitHub:

  • managed resources
  • compositions
  • APIs personalizadas de plataforma
  • functions
  • gestión de paquetes
  • dependencias entre providers
  • revisiones de providers
  • un ecosistema amplio de integraciones con servicios externos

Ese potencial también introduce un modelo operativo distinto.

Una configuración de GitHub basada en Crossplane normalmente implica:

Crossplane core
      +
gestión de paquetes
      +
paquete del provider de GitHub
      +
runtime del provider
      +
revisiones y actualizaciones del provider

Esto es apropiado cuando Crossplane ya forma parte estratégica de la plataforma.

Pero yo buscaba una opción más acotada.

github-platform-operator es un controlador independiente centrado en tareas recurrentes de una plataforma GitHub.

Deliberadamente intercambia:

cobertura amplia del provider
compositions
extensibilidad general del control plane

por:

un único operador enfocado
una API seleccionada y acotada
menos componentes de plataforma
semántica de adopción específica para GitHub
comportamiento de eliminación explícito
gestión compartida de rate limits

Esto no convierte a una alternativa en universalmente mejor que la otra.

Las hace apropiadas para entornos diferentes.

Enfoque Mejor encaje
Scripts Importaciones, migraciones y automatizaciones puntuales
Provider de GitHub para Terraform Aprovisionamiento basado en plan dentro de una plataforma Terraform
Provider de GitHub para Crossplane Organizaciones que ya construyen un control plane más amplio con Crossplane
github-platform-operator Gestión Kubernetes-native y enfocada de flujos recurrentes de plataforma en GitHub

El proyecto existe porque un operador enfocado puede ser más sencillo de introducir cuando GitHub es la principal plataforma externa que necesita este modelo.

Crossplane está optimizado para construir control planes extensibles. github-platform-operator está optimizado para un conjunto acotado de flujos recurrentes de plataforma en GitHub.


Una API deliberadamente más pequeña

Un provider generado a partir de una API externa amplia puede exponer una gran cantidad de recursos y campos.

Esa amplitud es valiosa.

También puede hacer que la API para el usuario se parezca mucho al esquema del provider remoto.

github-platform-operator adopta un enfoque seleccionado y acotado.

El proyecto no pretende gestionar todas las funcionalidades de GitHub.

Entre los objetivos que actualmente quedan fuera de alcance se encuentran:

runners de GitHub Actions
webhooks
ficheros arbitrarios dentro de repositorios
secretos de Dependabot
políticas de organización
billing
administración empresarial
la API completa de GitHub

Ese alcance más pequeño permite dedicar más esfuerzo de diseño a los flujos compatibles:

  • adopción segura
  • campos opcionales no gestionados
  • referencias legibles para humanos
  • comportamiento destructivo explícito
  • protección de dependencias
  • condiciones de status
  • suspensión
  • coordinación de rate limits
  • flujos de importación

Una API más pequeña no es automáticamente mejor.

Pero una API enfocada puede ofrecer una semántica más clara para su caso de uso previsto.


Arquitectura

A alto nivel, el proyecto sigue este flujo:

repositorio Git
      ↓
Argo CD o Flux
      ↓
API de Kubernetes
      ↓
github-platform-operator
      ↓
API REST de GitHub

Internamente:

GitHubProviderConfig
      ↓
resolución de credenciales
      ↓
cliente compartido de GitHub y puerta de rate limits
      ↓
reconciliadores específicos por recurso
      ↓
condiciones de status y requeues

El operador está construido con:

Go
Kubebuilder
controller-runtime
CustomResourceDefinitions
envtest
Helm
GitHub Actions
GoReleaser

La imagen del controlador se publica para múltiples plataformas.

El chart de Helm se publica como un artefacto OCI.

Los CRD se empaquetan dentro del chart.

El workflow de release publica de forma conjunta la GitHub Release, los binarios, la imagen del contenedor y el chart de Helm.


El status forma parte de la API

Un controlador no debería limitarse a ejecutar operaciones remotas.

También debería explicar lo que ha observado.

Los recursos exponen condiciones de Kubernetes con razones como:

RepositoryCreated
RepositoryUpdated
RepositoryAvailable
RulesetCreated
RulesetUpdated
TeamCreated
TeamAvailable
TeamMembershipConfigured
InvitationPending
ReconciliationSuspended
DependencyUnavailable
InvalidDesiredState
ReconciliationFailed

Esto permite que usuarios y automatizaciones puedan responder preguntas como:

  • ¿Se creó el recurso?
  • ¿Se adoptó un recurso ya existente?
  • ¿Sigue pendiente una invitación?
  • ¿Hay una dependencia referenciada que no está disponible?
  • ¿Está suspendido el provider?
  • ¿GitHub rechazó la operación?
  • ¿Se ha observado la última generación deseada?

El modelo de status forma parte de la experiencia del producto.

No es suficiente ocultar un error de la API remota únicamente en los logs del controlador.


Probar algo más que el happy path

Un test unitario del cliente de GitHub es útil, pero no es suficiente para un controlador.

El proyecto incluye varias capas de validación:

tests unitarios de Go
tests de contrato de las peticiones a GitHub
tests de reconciliación de controladores
envtest
verificación de CRD generados
generación de RBAC
lint y renderizado de Helm
tests end-to-end

Los tests de contrato validan el JSON serializado que se envía a GitHub.

Los tests del controlador cubren el comportamiento de reconciliación y las transiciones de estado.

La validación de Helm es importante porque el RBAC generado por Kubebuilder y el RBAC del chart son caminos de distribución diferentes.

Un controlador puede funcionar con make run y aun así fallar al instalarse mediante Helm si el chart no incluye los permisos introducidos por un reconciliador nuevo.

Ese tipo de problema reforzó una lección importante:

Una funcionalidad no está completa cuando el controlador compila. Está completa cuando su API, reconciliación, RBAC, empaquetado, documentación y proceso de actualización funcionan conjuntamente.


Lo que aprendí

Construir el operador implicó mucho más que mapear endpoints REST a métodos de Go.

Algunas de las lecciones más importantes estuvieron relacionadas con el diseño de APIs y ciclos de vida.

Diseñar la API es más difícil que llamar a la API

La petición remota puede ser sencilla.

Las preguntas difíciles son:

¿Qué significa omitir un campo?
¿Puede adoptarse el recurso?
¿Qué campos solo se utilizan durante la creación?
¿Cómo se detecta el drift?
¿Qué ocurre durante la eliminación?
¿Cómo se protegen las dependencias?
¿Qué identificadores deberían declarar los usuarios?
¿Qué status explica el estado actual?

La semántica de adopción debe ser intencional

Los recursos existentes son el caso normal durante una migración.

El operador debe distinguir entre:

campos observados
campos gestionados
campos borrados explícitamente
campos utilizados solo durante la creación

El comportamiento destructivo debe ser explícito

Orphan es el valor por defecto porque eliminar un recurso de Kubernetes no debería implicar silenciosamente destruir el recurso remoto.

El status forma parte de la usabilidad

Un controlador que realiza la petición correcta pero no explica su estado resulta difícil de operar.

Los rate limits pertenecen a la arquitectura

Cuando varios controladores comparten credenciales, el presupuesto de API debe coordinarse entre reconciliadores.

El empaquetado forma parte de la funcionalidad

Los CRD, RBAC, plantillas de Helm, versiones de imagen, automatización de releases y documentación deben permanecer alineados.

El enfoque es una decisión de producto

El proyecto podría exponer más recursos de GitHub.

Eso no significa que deba hacerlo.

El límite útil es el conjunto de flujos habituales de plataforma que pueden representarse con una semántica clara y segura.


Cuándo tiene sentido este enfoque

github-platform-operator encaja bien cuando:

  • Kubernetes ya actúa como control plane de la plataforma
  • Argo CD o Flux ya gestionan el estado deseado
  • la configuración de GitHub debe pasar por revisión mediante pull requests
  • el estado de repositorios y organización necesita reconciliación continua
  • los recursos existentes deben adoptarse de forma segura
  • los equipos quieren un operador enfocado en lugar de un framework más amplio de control plane
  • los platform engineers necesitan status, referencias y finalizers de Kubernetes

Puede no ser la opción adecuada cuando:

  • la configuración de GitHub ya funciona bien mediante Terraform
  • la organización ya está estandarizada en Crossplane
  • no existe un control plane de Kubernetes
  • solo se necesita una pequeña migración puntual
  • la funcionalidad necesaria de GitHub está deliberadamente fuera del alcance del proyecto

Una herramienta de plataforma útil debería dejar claros sus límites.


Reflexiones finales

El objetivo principal de github-platform-operator no es simplemente crear repositorios.

El objetivo es conseguir que la configuración habitual de una plataforma GitHub sea:

declarativa
revisable
adoptable
segura
observable
reconciliada continuamente

Los repositorios, equipos, permisos, rulesets, entornos, secretos y variables de GitHub influyen en cómo se construye y entrega el software.

Forman parte de la plataforma.

Cuando ese estado se vuelve declarativo, la pregunta cambia.

En lugar de preguntar:

¿Quién cambió esta opción en GitHub?

Puedes preguntar:

¿Qué estado debería imponer la plataforma?

El proyecto es open source:

https://github.com/pierinho13/github-platform-operator

La API completa, la guía de instalación, el comportamiento operativo, las políticas de eliminación y los ejemplos están documentados en el repositorio.

Las contribuciones, el feedback, las conversaciones sobre diseño de APIs, las mejoras de controladores, los tests y los nuevos flujos enfocados de plataforma GitHub son bienvenidos.

Si trabajas con plataformas Kubernetes, control planes GitOps, developer platforms o infraestructura cloud-native, puedes conocer mejor mi experiencia o ponerte en contacto.