mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-08 04:23:09 +00:00
chore: reproducible pipeline with dev containers (#10305)
# Which Problems Are Solved - The previous monorepo in monorepo structure for the login app and its related packages was fragmented, complicated and buggy. - The process for building and testing the login container was inconsistent between local development and CI. - Lack of clear documentation as well as easy and reliable ways for non-frontend developers to reproduce and fix failing PR checks locally. # How the Problems Are Solved - Consolidated the login app and its related npm packages by moving the main package to `apps/login/apps/login` and merging `apps/login/packages/integration` and `apps/login/packages/acceptance` into the main `apps/login` package. - Migrated from Docker Compose-based test setups to dev container-based setups, adding support for multiple dev container configurations: - `.devcontainer/base` - `.devcontainer/turbo-lint-unit` - `.devcontainer/turbo-lint-unit-debug` - `.devcontainer/login-integration` - `.devcontainer/login-integration-debug` - Added npm scripts to run the new dev container setups, enabling exact reproduction of GitHub PR checks locally, and updated the pipeline to use these containers. - Cleaned up Dockerfiles and docker-bake.hcl files to only build the production image for the login app. - Cleaned up compose files to focus on dev environments in dev containers. - Updated `CONTRIBUTING.md` with guidance on running and debugging PR checks locally using the new dev container approach. - Introduced separate Dockerfiles for the login app to distinguish between using published client packages and building clients from local protos. - Ensured the login container is always built in the pipeline for use in integration and acceptance tests. - Updated Makefile and GitHub Actions workflows to use `--frozen-lockfile` for installing pnpm packages, ensuring reproducible installs. - Disabled GitHub release creation by the changeset action. - Refactored the `/build` directory structure for clarity and maintainability. - Added a `clean` command to `docks/package.json`. - Experimentally added `knip` to the `zitadel-client` package for improved linting of dependencies and exports. # Additional Changes - Fixed Makefile commands for consistency and reliability. - Improved the structure and clarity of the `/build` directory to support seamless integration of the login build. - Enhanced documentation and developer experience for running and debugging CI checks locally. # Additional Context - See updated `CONTRIBUTING.md` for new local development and debugging instructions. - These changes are a prerequisite for further improvements to the CI pipeline and local development workflow. - Closes #10276
This commit is contained in:
186
apps/login/acceptance/idp/oidc/main.go
Normal file
186
apps/login/acceptance/idp/oidc/main.go
Normal file
@@ -0,0 +1,186 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/oidc/v3/example/server/exampleop"
|
||||
"github.com/zitadel/oidc/v3/example/server/storage"
|
||||
)
|
||||
|
||||
func main() {
|
||||
apiURL := os.Getenv("API_URL")
|
||||
pat := readPAT(os.Getenv("PAT_FILE"))
|
||||
domain := os.Getenv("API_DOMAIN")
|
||||
schema := os.Getenv("SCHEMA")
|
||||
host := os.Getenv("HOST")
|
||||
port := os.Getenv("PORT")
|
||||
|
||||
logger := slog.New(
|
||||
slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
|
||||
AddSource: true,
|
||||
Level: slog.LevelDebug,
|
||||
}),
|
||||
)
|
||||
|
||||
issuer := fmt.Sprintf("%s://%s:%s/", schema, host, port)
|
||||
redirectURI := fmt.Sprintf("%s/idps/callback", apiURL)
|
||||
|
||||
clientID := "web"
|
||||
clientSecret := "secret"
|
||||
storage.RegisterClients(
|
||||
storage.WebClient(clientID, clientSecret, redirectURI),
|
||||
)
|
||||
|
||||
storage := storage.NewStorage(storage.NewUserStore(issuer))
|
||||
router := exampleop.SetupServer(issuer, storage, logger, false)
|
||||
|
||||
server := &http.Server{
|
||||
Addr: ":" + port,
|
||||
Handler: router,
|
||||
}
|
||||
go func() {
|
||||
if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Fatalf("HTTP server error: %v", err)
|
||||
}
|
||||
log.Println("Stopped serving new connections.")
|
||||
}()
|
||||
|
||||
createZitadelResources(apiURL, pat, domain, issuer, clientID, clientSecret)
|
||||
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-sigChan
|
||||
|
||||
shutdownCtx, shutdownRelease := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer shutdownRelease()
|
||||
|
||||
if err := server.Shutdown(shutdownCtx); err != nil {
|
||||
log.Fatalf("HTTP shutdown error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func readPAT(path string) string {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pat, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return strings.Trim(string(pat), "\n")
|
||||
}
|
||||
|
||||
func createZitadelResources(apiURL, pat, domain, issuer, clientID, clientSecret string) error {
|
||||
idpID, err := CreateIDP(apiURL, pat, domain, issuer, clientID, clientSecret)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ActivateIDP(apiURL, pat, domain, idpID)
|
||||
}
|
||||
|
||||
type createIDP struct {
|
||||
Name string `json:"name"`
|
||||
Issuer string `json:"issuer"`
|
||||
ClientId string `json:"clientId"`
|
||||
ClientSecret string `json:"clientSecret"`
|
||||
Scopes []string `json:"scopes"`
|
||||
ProviderOptions providerOptions `json:"providerOptions"`
|
||||
IsIdTokenMapping bool `json:"isIdTokenMapping"`
|
||||
UsePkce bool `json:"usePkce"`
|
||||
}
|
||||
|
||||
type providerOptions struct {
|
||||
IsLinkingAllowed bool `json:"isLinkingAllowed"`
|
||||
IsCreationAllowed bool `json:"isCreationAllowed"`
|
||||
IsAutoCreation bool `json:"isAutoCreation"`
|
||||
IsAutoUpdate bool `json:"isAutoUpdate"`
|
||||
AutoLinking string `json:"autoLinking"`
|
||||
}
|
||||
|
||||
type idp struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
func CreateIDP(apiURL, pat, domain string, issuer, clientID, clientSecret string) (string, error) {
|
||||
createIDP := &createIDP{
|
||||
Name: "OIDC",
|
||||
Issuer: issuer,
|
||||
ClientId: clientID,
|
||||
ClientSecret: clientSecret,
|
||||
Scopes: []string{"openid", "profile", "email"},
|
||||
ProviderOptions: providerOptions{
|
||||
IsLinkingAllowed: true,
|
||||
IsCreationAllowed: true,
|
||||
IsAutoCreation: true,
|
||||
IsAutoUpdate: true,
|
||||
AutoLinking: "AUTO_LINKING_OPTION_USERNAME",
|
||||
},
|
||||
IsIdTokenMapping: false,
|
||||
UsePkce: false,
|
||||
}
|
||||
|
||||
resp, err := doRequestWithHeaders(apiURL+"/admin/v1/idps/generic_oidc", pat, domain, createIDP)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
idp := new(idp)
|
||||
if err := json.Unmarshal(data, idp); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return idp.ID, nil
|
||||
}
|
||||
|
||||
type activateIDP struct {
|
||||
IdpId string `json:"idpId"`
|
||||
}
|
||||
|
||||
func ActivateIDP(apiURL, pat, domain string, idpID string) error {
|
||||
activateIDP := &activateIDP{
|
||||
IdpId: idpID,
|
||||
}
|
||||
_, err := doRequestWithHeaders(apiURL+"/admin/v1/policies/login/idps", pat, domain, activateIDP)
|
||||
return err
|
||||
}
|
||||
|
||||
func doRequestWithHeaders(apiURL, pat, domain string, body any) (*http.Response, error) {
|
||||
data, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, apiURL, io.NopCloser(bytes.NewReader(data)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
values := http.Header{}
|
||||
values.Add("Authorization", "Bearer "+pat)
|
||||
values.Add("x-forwarded-host", domain)
|
||||
values.Add("Content-Type", "application/json")
|
||||
req.Header = values
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
Reference in New Issue
Block a user