port

package
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 30, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// ImageScanReportArtifact 는 산출물을 이름으로 묶는 CI(GitHub upload-artifact)의 묶음 이름이다.
	ImageScanReportArtifact = "trivy-report"
	// ImageScanReportFile 은 리포트 파일 경로다.
	ImageScanReportFile = "trivy-report.json"
)

이미지 스캔 리포트 산출물의 이름이다. 스캐폴딩이 파이프라인에 이 이름으로 남기고 실행 기록 동기화가 같은 이름으로 읽는다 — 한쪽만 바꾸면 건수가 조용히 빈다.

View Source
const (
	// SASTReportArtifact 는 GitHub upload-artifact 의 묶음 이름이다.
	SASTReportArtifact = "sast-report"
	// SASTReportFile 은 리포트 파일 경로다.
	SASTReportFile = "sast-report.json"
	// SecretScanReportArtifact 는 시크릿 탐지 리포트(gitleaks JSON)의 GitHub upload-artifact 묶음 이름이다.
	SecretScanReportArtifact = "secret-scan-report"
	// SecretScanReportFile 은 리포트 파일 경로다. 비밀값은 --redact 로 가려져 있다.
	SecretScanReportFile = "gitleaks-report.json"
	// DependencyScanReportArtifact 는 의존성 스캔 리포트(trivy fs JSON)의 GitHub upload-artifact 묶음 이름이다.
	DependencyScanReportArtifact = "dependency-scan-report"
	// DependencyScanReportFile 은 리포트 파일 경로다.
	DependencyScanReportFile = "dependency-scan-report.json"
)

소스 정적 분석 리포트 산출물의 이름이다. 이미지 스캔 리포트와 같은 규칙이다 — 스캐폴딩이 남기고 실행 기록 동기화가 같은 이름으로 읽는다.

View Source
const (
	// ScanServerVariable 은 스택 Trivy 서버 주소다. 정책이 아니라 스택이 정한 값이다.
	ScanServerVariable = "NULLUS_TRIVY_SERVER"
	// ScanImageVariable 은 CI 잡이 쓸 Trivy CLI 이미지다. 에어갭도 이름을 바꾸지 않는다 —
	// 노드의 레지스트리 미러가 번들에서 내준다. 플랫폼은 이 변수를 푸시하지 않는다.
	ScanImageVariable = "NULLUS_TRIVY_IMAGE"

	// ScanSeverityVariable 은 게이트가 막을 심각도 목록이다(Trivy --severity 형식).
	ScanSeverityVariable = "NULLUS_SCAN_SEVERITY"
	// ScanIgnoreUnfixedVariable 이 "true" 면 수정본 없는 취약점을 판정에서 뺀다.
	ScanIgnoreUnfixedVariable = "NULLUS_SCAN_IGNORE_UNFIXED"
	// ScanOnUnreachableVariable 이 "allow" 면 스캔을 못 돌려도 단계를 통과시킨다.
	ScanOnUnreachableVariable = "NULLUS_SCAN_ON_UNREACHABLE"

	// ScanPolicyConfigMapName 은 CI 변수 저장소가 없는 CI(Jenkins)가 정책을 읽는
	// 스택 네임스페이스의 ConfigMap 이다.
	ScanPolicyConfigMapName = "nullus-scan-policy"

	// SASTServerVariable 은 스택 SonarQube 의 클러스터 내 주소다. sonar-scanner 가
	// 이 이름의 환경변수를 그대로 읽는다.
	SASTServerVariable = "SONAR_HOST_URL"
	// SASTTokenVariable 은 분석 토큰이다. 스택 설치가 발급해 OpenBao 에 두고, 플랫폼이
	// 시크릿 CI 변수로 등록한다. sonar-scanner 가 이 이름을 그대로 읽는다.
	SASTTokenVariable = "SONAR_TOKEN"
	// SASTImageVariable 은 CI 잡이 쓸 sonar-scanner 이미지다. ScanImageVariable 과 같다.
	SASTImageVariable = "NULLUS_SONAR_SCANNER_IMAGE"
	// SASTOnGateFailureVariable 이 "warn" 이면 Quality Gate 가 실패해도 단계를 통과시킨다.
	SASTOnGateFailureVariable = "NULLUS_SAST_ON_GATE_FAILURE"
	// SecretDetectionVariable 은 시크릿 탐지 단계의 동작이다 — block(기본)·warn·off (nullus-plan#80).
	SecretDetectionVariable = "NULLUS_SECRET_DETECTION"
	// SecretScanImageVariable 은 CI 잡이 쓸 gitleaks 이미지다. ScanImageVariable 과 같다.
	SecretScanImageVariable = "NULLUS_GITLEAKS_IMAGE"
	// SecretScanBaseVariable 은 이번 푸시 직전 커밋이다 — 탐지 범위(신규 커밋만)의 시작점. CI 마다
	// 이름이 달라(CI_COMMIT_BEFORE_SHA · github.event.before · GIT_PREVIOUS_SUCCESSFUL_COMMIT) 렌더러가
	// 이 이름으로 옮겨 적는다. 플랫폼이 푸시하는 변수가 아니다.
	SecretScanBaseVariable = "NULLUS_SCAN_BASE"
)

이미지 스캔 단계가 읽는 변수 이름이다.

스캐폴딩은 이 이름으로 읽고, 정책 푸시는 같은 이름으로 싣는다 — 한쪽만 바꾸면 정책을 바꿔도 파이프라인은 기본값으로 판정한다.

View Source
const MaxCIArtifactBytes int64 = 32 << 20

MaxCIArtifactBytes 는 산출물 파일 하나를 읽을 때의 상한이다.

산출물은 사용자 파이프라인이 만든다. 크기를 믿고 통째로 메모리에 올리지 않는다. Trivy JSON 리포트는 큰 이미지도 수 MB 수준이다.

Variables

View Source
var ErrImageDeletionUnsupported = errors.New("이 레지스트리는 이미지 저장소 삭제를 지원하지 않습니다")

ErrImageDeletionUnsupported 는 이 레지스트리에서 이미지 저장소를 지울 수단이 없음을 알린다.

조용히 건너뛰지 않는 이유는, 사용자가 "이미지도 삭제" 를 골랐는데 아무 일도 일어나지 않으면 지워진 줄 알고 넘어가기 때문이다. Harbor·Nexus 는 삭제에 관리자 자격증명이 따로 필요해 파이프라인 삭제 경로에서는 아직 다루지 않는다.

View Source
var ErrNoScannerAvailable = errors.New(
	"이 스택에는 이미지 스캐너가 없습니다: 스택에 스캐너를 추가하거나 컨테이너 레지스트리를 Harbor 로 고르세요")

ErrNoScannerAvailable 은 이 스택에 이미지를 스캔할 수단이 없다는 뜻이다.

스캔 단계를 조용히 빼지 않는 이유는 ErrImageDeletionUnsupported 와 같다 — 사용자가 켠 단계가 아무 일도 하지 않으면 스캔된 줄 알고 넘어간다.

View Source
var ErrSCMUserNotFound = errors.New("SCM 사용자 계정을 찾지 못했습니다")

ErrSCMUserNotFound 는 그 이메일의 SCM 계정이 아직 없다는 뜻이다.

실패가 아니라 "아직" 이다 — OIDC 첫 로그인 전에는 계정이 존재하지 않는다. 호출부는 이것을 경고로 옮겨 담아야 한다.

View Source
var ErrSourceNotFound = errors.New("원본 저장소 또는 커밋을 찾지 못했습니다")

ErrSourceNotFound 는 원본 저장소나 그 커밋이 없다는 뜻이다.

View Source
var ErrSourceTooLarge = errors.New("원본이 너무 커서 가져올 수 없습니다")

ErrSourceTooLarge 는 원본이 한 커밋으로 옮기기에 너무 크다는 뜻이다.

View Source
var ErrSourceUnreachable = errors.New("원본 저장소에 닿지 못했습니다 — 원본을 가져오는 템플릿은 인터넷 연결이 필요합니다")

ErrSourceUnreachable 는 원본 저장소에 닿지 못했다는 뜻이다.

원본을 가져오는 템플릿은 인터넷 연결을 전제한다. 오프라인 설치에서 이 오류가 나면 그 템플릿을 쓸 수 없다는 뜻이지 일시적인 장애가 아니다.

View Source
var ErrSourceUnsupported = errors.New("가져올 수 없는 원본입니다")

ErrSourceUnsupported 는 가져올 수 없는 원본이라는 뜻이다 — 지원하지 않는 호스트·주소 모양이거나, 커밋 파일로 옮길 수 없는 항목(심볼릭 링크 등)이 있다. 템플릿을 고쳐야 낫는다.

View Source
var ErrStackToolsUnavailable = errors.New("스택의 도구에 닿을 수 없습니다")

ErrStackToolsUnavailable 은 이 스택의 도구에 닿을 수 없다는 뜻이다.

스택이 지워졌거나 아직 설치 중일 때다. 둘 다 "지금은 그 도구들에 아무것도 할 수 없다" 는 같은 상황이고, 호출부는 그것을 실패가 아니라 **할 수 없는 일**로 다뤄야 한다 — 스택이 사라진 파이프라인을 영영 못 지우게 되면 좀비가 남는다.

View Source
var ErrWebhookTargetPending = errors.New("webhook 대상 허용이 아직 SCM 에 반영되지 않았습니다")

ErrWebhookTargetPending 은 webhook 대상을 허용해 두었지만 SCM 이 아직 그 변경을 보지 못해 생성이 거부됐다는 뜻이다. 잠시 뒤 다시 걸면 된다 — GitLab 은 애플리케이션 설정을 프로세스마다 최대 1분 캐시한다.

Functions

func StageKey added in v0.6.0

func StageKey(name string) string

StageKey 는 CI 마다 다르게 적는 단계 이름을 비교용 키로 만든다.

Jenkins 는 stage('ImageScan'), GitLab·GitHub 은 잡 키 image-scan 으로 적는다. 대소문자만 맞추면 같은 단계가 서로 다르게 보여, 스캔 판정이 기록되지 않는다.

Types

type AccessTokenSpec

type AccessTokenSpec struct {
	Name   string
	Scopes []string
	// ExpiresInDays 가 0 이면 구현체 기본값을 쓴다.
	ExpiresInDays int
}

AccessTokenSpec 은 프로젝트 범위 토큰 발급 요청이다.

type BuildDelegate

type BuildDelegate interface {
	// DelegateBuild 는 러너의 실행을 시작시키고 그 실행의 주소를 돌려준다.
	DelegateBuild(ctx context.Context, opts DelegateBuildOpts) (runURL string, err error)
}

BuildDelegate 는 이미지 빌드를 스택의 CI 러너에 넘긴다.

ImagePreparer 와 짝을 이루는 반대편이다. ImagePreparer 는 플랫폼이 직접 빌드하는 장애 대응 경로(emergency_direct)이고, 이쪽은 스택 컴포넌트가 실행하는 일반 경로(stack_integrated)다 — CI 가 빌드해 레지스트리에 올리고 CD 도구가 클러스터에 반영한다. 플랫폼은 실행을 시작시키고 결과를 들여올 뿐 git clone·docker build·kubectl apply 를 하지 않는다.

type CDApplicationDeleter

type CDApplicationDeleter interface {
	// DeleteApplication 은 애플리케이션이 이미 없으면 성공으로 본다 —
	// 삭제의 목표는 "없는 상태" 이고, 없음을 오류로 올리면 재시도가 끝나지 않는다.
	DeleteApplication(ctx context.Context, kubeconfig []byte, namespace, name string) error
}

CDApplicationDeleter 는 CD 도구가 배포한 애플리케이션을 지운다.

도구 이름을 계약에 넣지 않는다. Argo CD 든 다른 것이든 "배포된 애플리케이션을 지운다" 는 같은 일이고, 도구가 바뀌면 구현체만 갈아 끼우면 된다.

애플리케이션만 지우면 Deployment·Service·HTTPRoute 가 클러스터에 남아 앱이 계속 돈다. 구현체는 그 도구의 방식대로 배포된 리소스까지 함께 걷어내야 한다 (Argo CD 는 정리 finalizer 를 붙인다).

type CDApplicationRefresher added in v0.6.0

type CDApplicationRefresher interface {
	EnsureFirstComparison(ctx context.Context, kubeconfig []byte, namespace, name string) error
}

CDApplicationRefresher 는 방금 만든 애플리케이션의 첫 비교가 저장소를 읽었는지 보고, 못 읽었으면 CD 도구에 다시 읽게 한다.

플랫폼은 저장소 자격증명과 애플리케이션을 한 번에 적용한다. CD 도구가 둘을 서로 다른 감시로 받으면 자격증명이 보이기 전에 첫 비교를 돌려 실패하고, 다음 정기 확인까지 그 상태로 남는다. 다시 읽게 해도 실패하면 그 이유를 돌려준다.

type CIArtifactLinker added in v0.6.0

type CIArtifactLinker interface {
	ArtifactWebURL(ref CIArtifactRef) string
}

CIArtifactReader 는 실행이 남긴 산출물 파일을 읽는다. CIArtifactLinker 는 산출물을 브라우저로 여는 주소를 만든다.

선택 기능이다. API 클라이언트는 클러스터 내부 주소로 붙으므로, 사람이 열 수 있는 외부 주소를 모르는 조회기는 빈 문자열을 돌려준다 — 죽은 링크보다 없는 편이 낫다.

type CIArtifactReader added in v0.6.0

type CIArtifactReader interface {
	// ReadArtifact 는 파일 내용을 돌려준다. 없으면 found=false 이고 오류가 아니다 —
	// 리포트가 없는 실행(스캐너에 닿지 못함, 보존 기간 만료)은 정상 경로다.
	ReadArtifact(ctx context.Context, ref CIArtifactRef) (data []byte, found bool, err error)
}

type CIArtifactRef added in v0.6.0

type CIArtifactRef struct {
	JobName string
	Branch  string
	Build   CIBuild
	Stage   CIStage
	// Name 은 산출물 묶음 이름, Path 는 그 안의 파일 경로다.
	Name string
	Path string
}

CIArtifactRef 는 읽을 산출물의 위치다.

CI 마다 산출물을 묶는 단위가 다르다 — Jenkins 는 빌드, GitLab 은 잡, GitHub 은 실행 안의 이름 붙은 묶음. 어댑터가 필요한 것을 골라 쓰도록 실행과 단계를 통째로 넘긴다.

type CIBuild

type CIBuild struct {
	// Number 는 화면에 보이는 실행 번호다(Jenkins 빌드 번호, GitLab iid, GitHub run_number).
	Number int
	// ID 는 CI API 가 실행을 가리키는 식별자다(GitLab pipeline id, GitHub run id).
	// 번호와 다를 수 있고, 산출물 조회는 이것으로 한다.
	ID string
	// Result 는 CI 가 보고한 결과다(SUCCESS/FAILURE/ABORTED/SKIPPED). 실행 중이면 빈 값이다.
	// SKIPPED(Jenkins 는 NOT_BUILT)는 실행이 아니다 — 아무 잡도 돌지 않은 빈 파이프라인이라
	// 동기화가 실행 기록으로 남기지 않는다.
	Result   string
	Building bool
	// StartedAt 은 빌드 시작 시각, Duration 은 실행 시간이다.
	// 실행 중인 빌드의 Duration 은 0 이다.
	StartedAt time.Time
	Duration  time.Duration
	// Stages 는 실행 안의 단계다. CI 가 단계 정보를 주지 않으면 비어 있다 —
	// 비어 있는 것과 "모두 성공" 은 다르다.
	Stages []CIStage
}

CIBuild 는 CI 서버가 실행한 빌드 하나다.

type CIBuildReader

type CIBuildReader interface {
	ListBuilds(ctx context.Context, jobName, branch string, limit int) ([]CIBuild, error)
}

CIBuildReader 는 CI 서버에서 빌드 이력을 읽는다.

플랫폼이 직접 배포하는 경로와 달리, GitOps 경로의 실행 기록은 CI 서버가 갖고 있다. 이것을 들이지 않으면 빌드가 성공해도 화면의 실행 통계가 영원히 0 으로 남는다.

type CIBuildTrigger

type CIBuildTrigger interface {
	TriggerBuild(ctx context.Context, jobName, branch string) (runURL string, err error)
}

CIBuildTrigger 는 CI 서버의 job 을 지금 실행시킨다.

webhook 은 커밋이 있을 때만 돈다. 사용자가 화면에서 "배포 실행" 을 누르는 것은 커밋 없이 지금 실행하겠다는 뜻이라, 트리거가 따로 필요하다.

돌려주는 주소는 사람이 열어 볼 실행 주소다. 플랫폼마다 모양이 다르므로(Jenkins 는 /job/…, GitLab 은 /-/pipelines/…) 호출부가 지어내지 않고 트리거가 준다. 모르면 빈 값이다.

type CICDGoldenPathRepository

type CICDGoldenPathRepository interface {
	GetByID(ctx context.Context, id string) (*domain.CICDGoldenPath, error)
	List(ctx context.Context) ([]*domain.CICDGoldenPath, error)
	Create(ctx context.Context, goldenPath *domain.CICDGoldenPath) error
	Update(ctx context.Context, goldenPath *domain.CICDGoldenPath) error
	Delete(ctx context.Context, id string) error
}

CICDGoldenPathRepository defines the interface for CI/CD Golden Path persistence.

type CICredentialResolver

type CICredentialResolver interface {
	ResolveCICredential(ctx context.Context, spec SCMTokenSpec) (user, secret string, err error)
}

CICredentialResolver 는 스택별 CI 서버 접속 자격증명을 돌려준다.

기동 시점에 고정할 수 없다 — CI 서버는 스택마다 따로 서고 관리자 비밀번호도 스택마다 다르게 생성된다(provisioning_secrets 가 OpenBao 에 넣는다). 고정 문자열을 쓰면 비어 있거나 다른 스택의 자격증명으로 붙게 된다.

SCMTokenSpec 을 재사용한다 — 스택·클러스터·조직·환경이라는 조회 축이 SCM 토큰과 완전히 같기 때문이다.

type CIJob

type CIJob struct {
	Name string
	URL  string
}

CIJob 은 만들어진 job 이다.

type CIJobProvisioner

type CIJobProvisioner interface {
	// EnsureJob 은 job 을 만들거나 이미 있으면 그대로 둔다(멱등).
	EnsureJob(ctx context.Context, spec CIJobSpec) (*CIJob, error)
	// DeleteJob 은 job 을 지운다. 이미 없으면 성공으로 본다.
	DeleteJob(ctx context.Context, name string) error
}

CIJobProvisioner 는 CI 서버에 job 을 만든다.

SCMProvisioner 와 분리한다. GitLab CI·GitHub Actions 는 파이프라인 정의를 푸시하면 자동으로 감지하지만, Jenkins 는 job 이 먼저 존재해야 한다 — Jenkinsfile 만 커밋해서는 아무 일도 일어나지 않는다. 그 차이를 흡수하는 자리이므로 이 포트를 지원하지 않는 플랫폼에서는 nil 이고, 호출부는 nil 이면 건너뛴다(기존 GitLab/GitHub 경로 무영향).

type CIJobSpec

type CIJobSpec struct {
	// Name 은 job 이름이다. 앱 이름과 같게 두어 화면에서 짝을 찾기 쉽게 한다.
	Name string
	// RepoCloneURL 은 CI 가 스캔할 저장소 주소다.
	RepoCloneURL string
	// RepoOwner / RepoName 은 organization 소스가 요구하는 분해된 형태다.
	RepoOwner string
	RepoName  string
	// ServerURL 은 SCM 서버의 루트 주소다. Gitea 소스는 리포 주소가 아니라
	// 서버 주소를 받아 API 로 브랜치를 훑는다.
	ServerURL string
	// CredentialID 는 CI 서버에 등록된 SCM 자격증명 식별자다.
	// 비어 있으면 익명으로 스캔한다 — private 리포에서는 실패한다.
	CredentialID string
	// PipelinePath 는 파이프라인 정의 파일의 리포 내 경로다.
	PipelinePath string
}

CIJobSpec 은 CI 서버에 만들 job 하나의 요청이다.

type CIPlatform

type CIPlatform string

CIPlatform 은 파이프라인을 실행하는 CI 플랫폼이다.

SCM 과 별개의 축이다. GitLab·GitHub 은 SCM 이 CI 를 겸하지만 Gitea 는 그렇지 않다 — 소스는 Gitea, 빌드는 Jenkins 처럼 갈린다. 하나로 묶어 두면 Gitea 스택에 .gitlab-ci.yml 이 깔리고 Jenkins 는 읽을 파일이 없어 영영 돌지 않는다.

const (
	// CIPlatformGitLabCI / CIPlatformGitHubActions 는 SCM 이 겸하는 CI 다.
	CIPlatformGitLabCI      CIPlatform = "gitlab-ci"
	CIPlatformGitHubActions CIPlatform = "github-actions"
	// CIPlatformJenkins 는 SCM 과 독립적으로 도는 CI 다.
	CIPlatformJenkins CIPlatform = "jenkins"
)

func DefaultCIPlatformFor

func DefaultCIPlatformFor(platform SCMPlatform) CIPlatform

DefaultCIPlatformFor 는 CI 를 명시하지 않았을 때 SCM 이 함의하는 CI 다.

Gitea 는 자체 CI 를 쓰지 않으므로 Jenkins 로 본다 — Gitea 스택에 .gitlab-ci.yml 을 깔면 아무것도 그것을 읽지 않는다.

type CIStage

type CIStage struct {
	// ID 는 CI 가 단계(잡)에 붙인 식별자다. 산출물을 잡 단위로 주는 CI(GitLab)에서
	// 리포트를 받을 때 쓴다. 단계에 따로 식별자가 없는 CI 는 비워 둔다.
	ID        string
	Name      string
	Status    CIStageStatus
	StartedAt time.Time
	Duration  time.Duration
}

CIStage 는 실행 하나 안의 단계다.

type CIStageStatus

type CIStageStatus string

CIStageStatus 는 단계 상태의 정규화된 어휘다.

CI 마다 어휘가 다르다 — Jenkins 는 SUCCESS/FAILED/IN_PROGRESS/NOT_EXECUTED, GitLab 은 success/failed/running/pending/skipped, GitHub Actions 는 status 와 conclusion 두 필드로 나눠 표현한다.

이 변환을 어댑터 경계에서 끝낸다. 도메인과 화면이 CI 별 어휘를 알게 되면 OSS 를 하나 늘릴 때마다 위쪽 계층이 모두 바뀐다.

const (
	CIStageSuccess CIStageStatus = "success"
	CIStageFailed  CIStageStatus = "failed"
	// CIStageCanceled 는 취소된 단계다. 실패와 구분한다 — 새 커밋이 앞선 실행을
	// 밀어내면 CI 가 취소하는데, 그것을 실패로 세면 스캔 판정이 error 로 남는다.
	CIStageCanceled CIStageStatus = "canceled"
	CIStageRunning  CIStageStatus = "running"
	CIStageQueued   CIStageStatus = "queued"
	// CIStageSkipped 는 조건 때문에 실행되지 않은 단계다.
	// 실패와 구분해야 한다 — 건너뛴 것은 잘못된 것이 아니다.
	CIStageSkipped CIStageStatus = "skipped"
	// CIStageUnknown 은 CI 가 상태를 알려주지 않은 경우다.
	// 성공으로 넘겨짚지 않는다 — 실행되지 않은 일을 성공이라 말하면 안 된다.
	CIStageUnknown CIStageStatus = "unknown"
)

func NormalizeStageStatus

func NormalizeStageStatus(raw string) CIStageStatus

NormalizeStageStatus 는 CI 가 쓰는 표현을 정규화된 어휘로 옮긴다.

어댑터가 자기 CI 의 표현을 여기 넘긴다. 모르는 값은 unknown 이다 — 넘겨짚으면 돌지 않은 단계가 성공으로 보인다.

type ClusterTarget

type ClusterTarget struct {
	Kubeconfig  []byte
	ClusterName string
}

type ClusterTargetProvider

type ClusterTargetProvider interface {
	GetTarget(ctx context.Context, clusterID string) (*ClusterTarget, error)
}

type CommitFile

type CommitFile struct {
	Path    string
	Content string
	// Executable 은 실행 권한이 있는 파일인지다(원본의 mvnw · entrypoint.sh 등). 잃으면 원본
	// Dockerfile 이 permission denied 로 죽는다. Gitea 의 contents API 는 파일 모드를 받지 않는다.
	Executable bool
}

CommitFile 은 커밋에 포함할 파일 하나다.

Content 는 파일의 바이트 그대로다. 외부 원본에서 가져온 글꼴·이미지처럼 텍스트가 아닌 파일도 담긴다 — 어댑터는 IsBinary 를 보고 SCM API 에 맞는 인코딩으로 보낸다.

func (CommitFile) IsBinary added in v0.6.0

func (f CommitFile) IsBinary() bool

IsBinary 는 텍스트로 보내면 깨지는 내용인지다.

SCM API 는 파일 내용을 JSON 문자열로 받는다. UTF-8 이 아닌 바이트는 JSON 으로 옮기는 순간 U+FFFD 로 바뀌고, NUL 은 받는 쪽이 텍스트로 다루지 못한다 — 둘 다 base64 로 보내야 그대로 커밋된다.

type CommitSpec

type CommitSpec struct {
	Branch  string
	Message string
	Files   []CommitFile
}

CommitSpec 은 여러 파일을 한 커밋으로 올리는 요청이다.

type DelegateBuildOpts

type DelegateBuildOpts struct {
	StackID string
	// JobName 은 CI 서버의 job 이름이다. 프로비저닝이 앱 이름으로 만든다.
	JobName string
	// Branch 는 실행할 브랜치다. multibranch job 은 브랜치가 하위 job 이라
	// 이것이 없으면 무엇을 실행할지 정해지지 않는다.
	Branch string
	// DeploymentID 는 진행 상황을 기록할 배포 기록이다.
	DeploymentID string
	// StepIndex 는 이 작업이 기록될 단계 번호다.
	StepIndex int
}

DelegateBuildOpts 는 어느 스택의 어느 job 을 실행할지다.

type DependencyScanResultRepository added in v0.6.0

type DependencyScanResultRepository interface {
	// Upsert 는 같은 ID 면 덮어쓴다. 같은 실행을 여러 번 동기화해도 기록이 늘지 않아야 한다.
	Upsert(ctx context.Context, result *domain.DependencyScanResult) error
	// ListByPipelineID 는 최신 결과부터 돌려준다.
	ListByPipelineID(ctx context.Context, pipelineID string) ([]*domain.DependencyScanResult, error)
	// Delete 는 기록을 지운다. 없으면 오류가 아니다 — 동기화가 취소된 실행마다 부른다.
	Delete(ctx context.Context, id string) error
}

DependencyScanResultRepository 는 의존성 취약점 스캔(SCA) 결과 요약을 보관한다(Security Dashboard 설계 §8 G1).

cicd 모듈이 소유한다. 대시보드는 PipelineSecurityReader 로 읽는다.

type DeploymentRepository

type DeploymentRepository interface {
	Create(ctx context.Context, deployment *domain.Deployment) error
	GetByID(ctx context.Context, id string) (*domain.Deployment, error)
	ListByPipelineID(ctx context.Context, pipelineID string) ([]*domain.Deployment, error)
	Update(ctx context.Context, deployment *domain.Deployment) error
	// Delete 는 기록 하나를 지운다. 없으면 오류가 아니다. 동기화가 실행이 아닌 것(SKIPPED 파이프라인)을
	// 예전에 실패로 남긴 기록을 거둘 때 쓴다.
	Delete(ctx context.Context, id string) error
}

DeploymentRepository defines the interface for deployment persistence.

type GatewayLocator added in v0.6.0

type GatewayLocator interface {
	// LocateGateway 는 namespace 의 게이트웨이 가운데 앱이 붙을 것을 고른다. preferred 가 있으면 그것을,
	// 없으면 하나뿐인 것(또는 Nullus 가 만든 것 하나)을 돌려준다. 고를 수 없으면 빈 문자열이다.
	LocateGateway(ctx context.Context, kubeconfig []byte, namespace, preferred string) (string, error)
}

GatewayLocator 는 스택 네임스페이스에 실제로 있는 게이트웨이를 찾는다.

스택은 게이트웨이를 만드는 길이 둘이고 이름 규칙도 둘이다 — 화면 설치는 `<스택 이름>-gateway`, 화면이 게이트웨이 매니페스트를 보내지 않는 설치(런북 stack-up 등)는 서버 기본값이 접근 도메인에서 만든다. 앱 HTTPRoute 가 이름을 짐작하면 둘 중 하나는 틀리고, 틀린 이름을 가리키는 라우트는 어느 컨트롤러도 집지 않아 status 도 비고 주소는 404 다.

type GroupSpec

type GroupSpec struct {
	Name        string
	Path        string
	Description string
}

GroupSpec 은 그룹 생성 요청이다.

type ImagePreparer

type ImagePreparer interface {
	PrepareImage(ctx context.Context, opts PrepareImageOpts) (imageRef string, err error)
}

type ImageRegistryResolver

type ImageRegistryResolver interface {
	Resolve(ctx context.Context, spec ImageTargetSpec) (*ImageTarget, error)
}

ImageRegistryResolver 는 스택 구성에 맞는 이미지 저장 위치를 결정한다.

type ImageRepositoryDeleter

type ImageRepositoryDeleter interface {
	DeleteImageRepository(ctx context.Context, target *ImageTarget) error
}

ImageRepositoryDeleter 는 레지스트리에서 이미지 저장소를 통째로 지운다.

태그 하나가 아니라 저장소 전체가 대상이다. 파이프라인을 지우는 맥락에서는 그 앱의 이미지가 전부 필요 없어지기 때문이다.

type ImageScanResultRepository added in v0.6.0

type ImageScanResultRepository interface {
	// Upsert 는 같은 ID 면 덮어쓴다. 같은 실행을 여러 번 동기화해도 기록이
	// 늘지 않아야 한다.
	Upsert(ctx context.Context, result *domain.ImageScanResult) error
	// ListByPipelineID 는 최신 결과부터 돌려준다.
	ListByPipelineID(ctx context.Context, pipelineID string) ([]*domain.ImageScanResult, error)
	// GetByID 는 결과 하나다. 없으면 nil, nil 이다.
	GetByID(ctx context.Context, id string) (*domain.ImageScanResult, error)
	// Delete 는 기록을 지운다. 없으면 오류가 아니다 — 동기화가 반복해서 부른다.
	Delete(ctx context.Context, id string) error
}

ImageScanResultRepository 는 이미지 스캔 결과를 보관한다.

cicd 모듈이 소유한다. 대시보드(#65)는 이 테이블을 직접 조회하지 않고 cicd 의 공개 경로로 읽는다.

type ImageTarget

type ImageTarget struct {
	Kind RegistryKind
	// Host 는 레지스트리 호스트다 (예: "registry.nullus.local").
	Host string
	// Repository 는 태그를 뺀 완전한 이미지 경로다
	// (예: "registry.nullus.local/acme/myapp").
	Repository string
	// UsernameVar / PasswordVar 는 CI 가 로그인에 쓸 변수 이름이다.
	// GitLab 프로젝트 레지스트리는 내장 변수를, 외부 레지스트리는
	// 파이프라인에 등록된 변수를 가리킨다.
	UsernameVar string
	PasswordVar string
	// RequiredVariables 는 파이프라인에 미리 등록돼야 하는 변수들이다.
	// 내장 변수만 쓰는 구성에서는 비어 있다.
	RequiredVariables []string
}

ImageTarget 은 CI 가 이미지를 올릴 위치와 인증 방법이다.

로그인 명령을 문자열로 담지 않는다 — 셸 조각이 포트에 새면 CI 플랫폼을 바꿀 때 포트까지 흔들린다. 렌더러가 이 구조를 보고 스크립트를 만든다.

type ImageTargetSpec

type ImageTargetSpec struct {
	// AppName 은 애플리케이션 이름이다.
	AppName string
	// SCMProjectPath 는 앱의 SCM 프로젝트 전체 경로다 (예: "acme/myapp").
	// SCM 프로젝트 레지스트리를 쓰는 구성에서만 의미가 있다.
	SCMProjectPath string
	// SCMRegistryURL 은 SCM 이 알려준 프로젝트 레지스트리 경로다.
	SCMRegistryURL string
	// OrgPath 는 조직 그룹 경로다. Harbor 프로젝트 이름 등에 쓴다.
	OrgPath string
}

ImageTargetSpec 은 이미지 저장 위치를 묻는 요청이다.

type KubeconfigProvider

type KubeconfigProvider interface {
	GetKubeconfig(ctx context.Context, clusterID string) ([]byte, error)
}

type ManagedSecretDeleter added in v0.6.0

type ManagedSecretDeleter interface {
	DeleteManagedSecret(ctx context.Context, kubeconfig []byte, namespace, name string) error
}

ManagedSecretDeleter 는 플랫폼이 파이프라인 몫으로 만든 Secret 을 이름으로 지운다.

이름이 같아도 플랫폼 표시(managed-by)가 없는 Secret 은 지우지 않는다 — 사용자가 같은 이름으로 만든 것일 수 있다. 이미 없는 것은 성공으로 본다.

type ManifestApplier

type ManifestApplier interface {
	Apply(ctx context.Context, kubeconfig []byte, manifests []string) error
	ApplyWithTracking(ctx context.Context, kubeconfig []byte, manifests []string, deploymentID string, stepOffset ...int) error
}

type OrgMemberProvisioner

type OrgMemberProvisioner interface {
	// EnsureOrgMember 는 이메일로 찾은 사용자를 조직에 넣는다(멱등).
	EnsureOrgMember(ctx context.Context, org, email string) error
}

OrgMemberProvisioner 는 사람을 조직에 넣는다.

플랫폼이 만든 저장소는 자동화 계정 소유의 private 조직 안에 있어서, 그대로 두면 정작 그 저장소에 소스를 밀어야 할 사람이 보지도 못한다. 지원하지 않는 플랫폼에서는 nil 이며, 호출부는 nil 이면 건너뛴다.

type PipelineConfigurator

type PipelineConfigurator interface {
	// SetProjectVariable 은 변수를 등록하거나 이미 있으면 갱신한다.
	SetProjectVariable(ctx context.Context, projectID string, v ProjectVariable) error
	// CreateProjectAccessToken 은 프로젝트 범위 토큰을 발급한다.
	// 값은 발급 시점에만 읽을 수 있다.
	CreateProjectAccessToken(ctx context.Context, projectID string, spec AccessTokenSpec) (string, error)
}

PipelineConfigurator 는 파이프라인 실행에 필요한 설정을 프로젝트에 건다.

SCMProvisioner 와 분리한다 — 저장소 프로비저닝과 CI 설정은 플랫폼에 따라 제공 주체가 다를 수 있다(예: GitHub + 외부 CI).

type PipelineCredentialObject added in v0.6.0

type PipelineCredentialObject struct {
	Namespace       string
	Name            string
	RemoteKeyPrefix string
}

PipelineCredentialObject 는 파이프라인 자격증명을 클러스터에 싣는 객체(ExternalSecret)다.

RemoteKeyPrefix 는 그 객체가 가리킬 수 있는 금고 경로의 접두사다. 플랫폼 표시가 붙기 전에 만든 객체는 이것으로 이 파이프라인 것인지 가린다 — 같은 네임스페이스에서 스택 모듈도 같은 SecretStore 로 ExternalSecret 을 만든다.

type PipelineCredentialObjectDeleter added in v0.6.0

type PipelineCredentialObjectDeleter interface {
	DeleteCredentialObject(ctx context.Context, kubeconfig []byte, obj PipelineCredentialObject) error
}

PipelineCredentialObjectDeleter 는 PipelineCredentialObject 를 클러스터에서 지운다. 이 파이프라인 것이 아니면 이름이 같아도 지우지 않는다. 없으면 성공이다.

type PipelineCredentialPlane

type PipelineCredentialPlane interface {
	Provision(ctx context.Context, app string, vars []PipelineVariable) (manifest string, err error)
	// CredentialObject 는 Provision 이 만든 클러스터 객체의 자리다. 파이프라인을 지울 때
	// 클러스터에서 그것을 찾아 지운다. 이름이 경로로 쓸 수 없으면 오류다.
	CredentialObject(app string) (PipelineCredentialObject, error)
	// Revoke 는 Provision 이 금고에 쓴 값을 지운다. 이미 없으면 성공이다.
	Revoke(ctx context.Context, app string) error
}

PipelineCredentialPlane 은 파이프라인 자격증명을 준비하고 그것을 클러스터에 반영할 매니페스트를 돌려준다.

적용은 하지 않는다 — 클러스터 접근은 유스케이스가 한곳에서 맡는다.

type PipelineNameLookup added in v0.6.0

type PipelineNameLookup interface {
	OtherPipelinesNamed(ctx context.Context, stackID, name, excludeID string) ([]string, error)
}

PipelineNameLookup 은 같은 스택에서 같은 이름(앞뒤 공백 무시)을 쓰는 다른 파이프라인의 ID 를 모두 돌려준다.

이름으로 찾는 자원(CI 자격증명)을 지우기 전에 그것을 함께 쓰는 파이프라인이 있는지 본다. 목록 조회(ListByStackID)는 최근 100개만 돌려줘 오래된 레코드를 놓친다.

type PipelineRepository

type PipelineRepository interface {
	Create(ctx context.Context, pipeline *domain.Pipeline) error
	GetByID(ctx context.Context, id string) (*domain.Pipeline, error)
	List(ctx context.Context, orgID string) ([]*domain.Pipeline, error)
	ListByStackID(ctx context.Context, stackID string) ([]*domain.Pipeline, error)
	Update(ctx context.Context, pipeline *domain.Pipeline) error
	Delete(ctx context.Context, id string) error
}

PipelineRepository defines the interface for pipeline persistence.

type PipelineSecurityReader added in v0.6.0

type PipelineSecurityReader interface {
	// ListPipelineSecurity 는 조직의 파이프라인을 최근 만든 것부터 최대 limit 개 돌려준다. 결과가 없는
	// 칸은 nil 이다. RunVersions 는 채우지 않는다(실행 ID 규칙은 동기화 유스케이스가 안다).
	ListPipelineSecurity(ctx context.Context, orgID string, limit int) ([]*domain.PipelineSecuritySnapshot, error)
}

PipelineSecurityReader 는 조직 파이프라인의 단계별 최신 보안 결과를 한 번에 읽는다(Security Dashboard 설계 §5). 대시보드가 파이프라인마다 네 결과를 따로 묻지 않게 한다.

type PipelineTemplateRepository

type PipelineTemplateRepository interface {
	GetByID(ctx context.Context, id string) (*domain.PipelineTemplate, error)
	List(ctx context.Context) ([]*domain.PipelineTemplate, error)
	Create(ctx context.Context, tmpl *domain.PipelineTemplate) error
	Update(ctx context.Context, tmpl *domain.PipelineTemplate) error
	Delete(ctx context.Context, id string) error
}

PipelineTemplateRepository defines the interface for pipeline template persistence.

type PipelineVariable

type PipelineVariable struct {
	Key   string
	Value string
}

PipelineVariable 은 파이프라인이 환경변수로 읽을 값 하나다.

type PipelineVariableWriter added in v0.6.0

type PipelineVariableWriter interface {
	SetPipelineVariable(ctx context.Context, projectID, key, value string) error
}

PipelineVariableWriter 는 비밀이 아닌 파이프라인 변수를 프로젝트에 싣는다.

PipelineConfigurator.SetProjectVariable 과 나눈다 — GitHub 에서 그쪽은 Actions 시크릿이라, 워크플로가 ${{ vars.X }} 로 읽는 값을 넣을 수 없고 로그에서도 *** 로 가려진다. projectID 는 SCM 의 전체 경로다(GitLab 그룹/앱, GitHub owner/repo).

type PrepareImageOpts

type PrepareImageOpts struct {
	GitRepoURL       string
	DockerfilePath   string
	DockerContext    string
	ImageName        string
	ClusterName      string
	DeploymentID     string
	RegistryURL      string
	RegistryUsername string
	RegistryPassword string
}

type ProjectSpec

type ProjectSpec struct {
	Name string
	Path string
	// GroupID 는 생성 시 네임스페이스 지정에 쓴다(숫자 ID).
	GroupID string
	// GroupPath 는 조회 시 경로 조합에 쓴다("{GroupPath}/{Path}").
	// 비면 개인 네임스페이스로 간주해 Path 만으로 조회한다.
	GroupPath   string
	Description string
	// Visibility 는 private | internal | public. 비면 private.
	Visibility string
	// InitReadme 가 true 면 기본 브랜치가 즉시 생성된다.
	// 브랜치가 없으면 파일 커밋이 실패하므로 첫 커밋 전에는 켜야 한다.
	InitReadme bool
}

ProjectSpec 은 프로젝트 생성 요청이다.

GroupID 가 비면 인증 사용자의 개인 네임스페이스에 만들어진다.

type ProjectVariable

type ProjectVariable struct {
	Key   string
	Value string
	// Masked 는 job 로그에서 값을 가린다. 자격증명에는 반드시 켠다.
	Masked bool
	// Protected 를 켜면 보호 브랜치에서만 노출된다.
	Protected bool
}

ProjectVariable 은 파이프라인에 등록할 CI/CD 변수다.

func ScanPolicyVariables added in v0.6.0

func ScanPolicyVariables(p domain.ScanPolicy) []ProjectVariable

ScanPolicyVariables 는 정책을 CI 가 읽는 변수로 옮긴다.

가리지 않는다. 정책은 비밀이 아니고, GitLab 은 8자 미만 값(true·allow)의 마스킹 등록을 통째로 거부해 변수가 아예 생기지 않는다.

type RegistryCredentialResolver

type RegistryCredentialResolver interface {
	Resolve(ctx context.Context, variables []string) (map[string]string, error)
}

RegistryCredentialResolver 는 CI 가 레지스트리 로그인에 쓸 변수 값을 푼다.

요청한 변수 중 아는 것만 채워 돌려준다. 모르는 것은 손대지 않는다 — 조용히 빈 값을 채우면 CI 가 엉뚱한 자격증명으로 로그인을 시도한다.

type RegistryKind

type RegistryKind string

RegistryKind 는 이미지 레지스트리 백엔드의 종류다.

스택 구성에 따라 이미지가 저장될 곳이 달라진다. GitLab 스택이면 SCM 프로젝트에 딸린 레지스트리를, Harbor 를 고른 스택이면 Harbor 프로젝트를 쓴다. CI 스크립트를 특정 레지스트리에 맞춰 쓰지 않기 위해 이 구분을 포트로 올린다.

const (
	// RegistryKindSCMProject 는 소스 저장소 플랫폼이 프로젝트마다 제공하는
	// 레지스트리다 (GitLab Container Registry). 저장소와 수명이 같다.
	RegistryKindSCMProject RegistryKind = "scm_project"
	// RegistryKindHarbor 는 독립 설치된 Harbor 다.
	RegistryKindHarbor RegistryKind = "harbor"
	// RegistryKindNexus 는 독립 설치된 Nexus 의 Docker 커넥터다.
	RegistryKindNexus RegistryKind = "nexus"
	// RegistryKindGHCR 은 GitHub Container Registry 다.
	//
	// 다른 종류와 달리 push 자격증명을 등록할 필요가 없다 — GitHub Actions 의
	// 내장 GITHUB_TOKEN 이 packages:write 권한을 가질 수 있다.
	RegistryKindGHCR RegistryKind = "ghcr"
	// RegistryKindExternal 은 그 밖의 클러스터 외부 레지스트리다 (ECR 등).
	RegistryKindExternal RegistryKind = "external"
)

type RepositoryLocation added in v0.6.0

type RepositoryLocation struct {
	Platform SCMPlatform `json:"platform"`
	// GroupPath 는 저장소가 들어갈 그룹(GitLab · Gitea) 또는 조직(GitHub)이다.
	GroupPath string `json:"group_path"`
	// WebBaseURL 은 브라우저가 여는 SCM 주소다. 접근 도메인이 없어 모르면 비어 있다.
	WebBaseURL string `json:"web_base_url"`
}

RepositoryLocation 은 스택에 묶인 파이프라인의 앱 저장소가 만들어질 자리다.

저장소는 언제나 <GroupPath>/<앱 이름> 으로 만들어진다. 앱 배포 화면이 그 주소를 미리 보여 주는 데 쓴다 — 전에는 화면이 저장소 경로를 입력받았지만 서버가 그 값을 쓰지 않아, 입력한 주소와 실제 저장소가 달랐다.

type RepositoryLocator added in v0.6.0

type RepositoryLocator interface {
	RepositoryLocation(ctx context.Context, stackID string) (*RepositoryLocation, error)
}

RepositoryLocator 는 스택의 앱 저장소 자리를 알려 준다. 토큰을 발급하거나 SCM 에 붙지 않는다.

type SASTResultRepository added in v0.6.0

type SASTResultRepository interface {
	// Upsert 는 같은 ID 면 덮어쓴다. 같은 실행을 여러 번 동기화해도 기록이 늘지 않아야 한다.
	Upsert(ctx context.Context, result *domain.SASTResult) error
	// ListByPipelineID 는 최신 결과부터 돌려준다.
	ListByPipelineID(ctx context.Context, pipelineID string) ([]*domain.SASTResult, error)
}

SASTResultRepository 는 소스 정적 분석 결과 요약을 보관한다.

cicd 모듈이 소유한다. 대시보드(#65)는 이미지 스캔과 같이 cicd 의 공개 경로로 읽는다.

type SASTTokenResolver added in v0.6.0

type SASTTokenResolver interface {
	AnalysisToken(ctx context.Context) (string, error)
}

SASTTokenResolver 는 스택 SonarQube 의 분석 토큰을 읽는다. 발급하지 않고 읽기만 한다.

type SCMBundle

type SCMBundle struct {
	Provisioner SCMProvisioner
	Pipeline    PipelineConfigurator
	Registry    ImageRegistryResolver
	// Images 는 이미지 저장소를 지울 수단이다. 지원하지 않는 레지스트리에서는
	// nil 이며, 호출부는 이를 ErrImageDeletionUnsupported 와 같게 다뤄야 한다
	// — 조용히 건너뛰면 사용자는 이미지가 지워진 줄 안다.
	Images ImageRepositoryDeleter

	// CIJobs 는 CI 서버에 job 을 만드는 수단이다.
	//
	// GitLab CI·GitHub Actions 는 파이프라인 정의를 푸시하면 자동 감지하므로
	// nil 이다. Jenkins 는 job 이 먼저 존재해야 하므로 이 자리가 채워진다.
	// 호출부는 nil 이면 건너뛴다 — 기존 경로 무영향.
	CIJobs CIJobProvisioner
	// Webhooks 는 저장소에 push webhook 을 거는 수단이다.
	// Jenkins multibranch 가 새 커밋을 알려면 필요하다. 지원하지 않으면 nil.
	Webhooks SCMWebhookProvisioner
	// CITrigger 는 CI 서버의 job 을 지금 실행시킨다. 지원하지 않으면 nil.
	//
	// 스택에 묶인 파이프라인의 "배포 실행" 이 이 자리로 온다 — 플랫폼이
	// 빌드하지 않고 러너에게 넘긴다.
	CITrigger CIBuildTrigger
	// CIBuilds 는 CI 서버의 빌드 이력을 읽는다. 지원하지 않으면 nil.
	CIBuilds CIBuildReader
	// CIArtifacts 는 실행이 남긴 산출물(스캔 리포트)을 읽는다. 지원하지 않으면 nil —
	// 스캔 판정은 단계 결과만으로 남고 건수는 비어 있다.
	CIArtifacts CIArtifactReader
	// ScanPolicy 는 스캔 정책을 파이프라인이 읽는 자리에 싣는다. 정책을 실을
	// 경로가 배선되지 않은 스택에서는 nil 이다.
	ScanPolicy ScanPolicyPublisher
	// CIBaseURL 은 CI 서버의 주소다. webhook 대상 주소를 만드는 데 쓴다.
	CIBaseURL string
	// SCMInClusterURL 은 클러스터 안에서 SCM 에 닿는 주소다.
	//
	// Provisioner 의 base URL 과 구분한다 — 그쪽은 API 서버가 쓰는 주소라
	// 로컬 실행에서 우회 주소(localhost 포트포워드 등)일 수 있다. 반면 이 값은
	// Jenkins·Argo CD 처럼 클러스터 안에서 도는 소비자가 쓰므로 항상 서비스
	// DNS 여야 한다. 둘을 같은 값으로 두면 job 이
	// "Unknown server: http://localhost:3000" 으로 죽는다.
	SCMInClusterURL string
	// Credentials 는 CI 변수 저장소가 없는 SCM(Gitea)에서 파이프라인 자격증명을
	// OpenBao → ESO → K8s Secret 평면으로 나른다. 지원하지 않으면 nil.
	Credentials PipelineCredentialPlane

	// CDApplications 는 CD 도구가 배포한 애플리케이션을 지우는 수단이다.
	// 지원하지 않는 도구에서는 nil 이며, 호출부는 그것을 밝히고 넘어간다.
	CDApplications CDApplicationDeleter
	// CDRefresher 는 방금 만든 애플리케이션의 첫 비교를 확인한다. 지원하지 않으면 nil.
	CDRefresher CDApplicationRefresher
	// CDWebhooks / CDWebhookURL 은 저장소가 새 커밋을 CD 도구에 바로 알리게 하는
	// 수단과 주소다. 비어 있으면 CD 도구는 정기 확인(Argo CD 기본 3분)으로만 새
	// 커밋을 안다 — CI 가 배포 태그를 되커밋해도 그만큼 늦게 배포된다.
	CDWebhooks   SCMWebhookProvisioner
	CDWebhookURL string

	// OrgMembers 는 사람을 조직에 넣는 수단이다. 지원하지 않으면 nil.
	//
	// 플랫폼이 만든 저장소는 자동화 계정 소유의 private 조직 안에 있어서,
	// 그대로 두면 정작 소스를 밀어야 할 사람이 보지도 못한다.
	OrgMembers OrgMemberProvisioner

	// RegistryCredentials 는 스택이 설치한 레지스트리(Harbor·Nexus)의 자격증명을
	// 푼다. 그 값은 스택 설치가 OpenBao 에 만들어 두므로 사용자에게 다시 받을
	// 이유가 없다. 플랫폼이 소유하지 않는 레지스트리에서는 아무것도 돌려주지 않는다.
	RegistryCredentials RegistryCredentialResolver

	// Platform 은 이 묶음이 향하는 SCM 플랫폼이다.
	// 파이프라인 파일 형식이 여기서 갈리므로 렌더러까지 전달돼야 한다.
	Platform SCMPlatform
	// RepoAccessToken 은 플랫폼이 리포 범위 토큰 발급을 지원하지 않을 때
	// Argo CD·이미지 pull 인증에 재사용할 토큰이다.
	//
	// GitLab 에서는 비어 있다 — 프로젝트마다 최소 권한 토큰을 따로 발급한다.
	// GitHub 에는 리포 단위 토큰 API 가 없어 조직 PAT 를 그대로 쓴다.
	RepoAccessToken string

	// GroupPath 는 프로젝트가 만들어질 네임스페이스다.
	// GitLab 은 그룹 경로, GitHub 은 organization/사용자 이름이다.
	GroupPath string
	// CDNamespace 는 CD 도구가 설치된 네임스페이스다.
	//
	// 애플리케이션 리소스는 **여기**에 산다. 배포 대상 네임스페이스(앱이 서는
	// 곳)와 다르다 — 둘을 혼동하면 삭제가 없는 곳을 뒤지고 성공으로 끝난다.
	CDNamespace string
	// ClusterID 는 Application 을 적용할 클러스터다.
	ClusterID string
	// AccessDomain / GatewayName 은 배포된 앱을 외부에 노출할 때 쓴다.
	// 비어 있으면 앱은 클러스터 내부에서만 접근 가능하다.
	AccessDomain string
	// ImageScannerEndpoint 는 스택 Trivy 서버의 클러스터 내 주소다.
	// 스캐너를 고르지 않은 스택에서는 비어 있고, 그때는 스캐폴딩이
	// 이미지 스캔 단계를 만들지 않는다.
	ImageScannerEndpoint string
	// ImageScannerJavaDBRepository 는 에어갭 설치에서 CI 잡의 trivy client 가 Java DB 를
	// 받을 내부 미러다. 클러스터 안 CI(GitLab 러너·Jenkins 에이전트) 번들에만 싣는다.
	ImageScannerJavaDBRepository string
	// DependencyScanEndpoint 는 의존성 스캔(SCA)이 붙을 스택 Trivy 서버 주소다. 비면 단계를 만들지 않는다.
	DependencyScanEndpoint string
	// SASTServerEndpoint 는 스택 SonarQube 의 클러스터 내 주소다. SonarQube 를 고르지 않은
	// 스택에서는 비어 있고, 그때는 스캐폴딩이 소스 정적 분석 단계를 만들지 않는다.
	SASTServerEndpoint string
	// SASTWebURL 은 사람이 여는 SonarQube 주소다(https://sonarqube.<접근 도메인>). 분석 결과의
	// 링크를 만든다. SonarQube 가 없거나 접근 도메인이 없으면 비어 있다.
	SASTWebURL string
	// SASTToken 은 CI 가 분석에 쓸 토큰을 푼다. 스택 설치가 OpenBao 에 둔 값이다.
	// SonarQube 가 없거나 시크릿 백엔드가 배선되지 않았으면 nil 이다.
	SASTToken   SASTTokenResolver
	GatewayName string
}

SCMBundle 은 특정 스택에 묶인 프로비저닝 도구 묶음이다.

GitLab 주소·토큰·레지스트리 종류가 모두 스택마다 다르므로 기동 시점에 하나로 만들 수 없다. 요청 시점에 스택을 보고 조립한다.

type SCMBundleFactory

type SCMBundleFactory interface {
	For(ctx context.Context, stackID string) (*SCMBundle, error)
}

SCMBundleFactory 는 스택에 맞는 도구 묶음을 조립한다.

type SCMConnection

type SCMConnection struct {
	Platform SCMPlatform
	// Owner 는 리포지토리가 만들어질 GitHub Organization 또는 사용자 계정이다.
	Owner string
	// APIBaseURL 은 GitHub Enterprise Server 의 API 주소다.
	// 비면 어댑터가 github.com 을 쓴다.
	APIBaseURL string
}

SCMConnection 은 외부 SCM 에 붙기 위한 조직 단위 설정이다.

스택 안에 설치되는 GitLab 과 달리 GitHub 은 주소도 소유자도 우리가 정할 수 없다. 사용자가 등록한 값을 읽어야만 어느 org 에 리포를 만들지 알 수 있다.

type SCMConnectionReader

type SCMConnectionReader interface {
	// GetConnection 은 설정을 읽는다. 등록된 것이 없으면 nil, nil 을 돌려준다.
	GetConnection(ctx context.Context, orgID string, platform SCMPlatform) (*SCMConnection, error)
}

SCMConnectionReader 는 조직에 등록된 SCM 연동 설정을 읽는다.

type SCMGroup

type SCMGroup struct {
	ID       string `json:"id"`
	Name     string `json:"name"`
	FullPath string `json:"full_path"`
	WebURL   string `json:"web_url"`
}

SCMGroup 은 소스 저장소 플랫폼의 그룹(네임스페이스)이다.

type SCMPlatform

type SCMPlatform string

SCMPlatform 은 스택이 쓰는 소스 저장소 플랫폼이다.

저장소를 만드는 방법도, 파이프라인 파일 형식도, 토큰을 얻는 경로도 플랫폼마다 다르다. 어댑터를 고른 뒤에도 렌더러까지 이 값이 따라가야 해서 포트로 올린다.

const (
	// SCMPlatformGitLab 은 Nullus 가 스택에 직접 설치한 GitLab 이다.
	SCMPlatformGitLab SCMPlatform = "gitlab"
	// SCMPlatformGitHub 은 외부 SaaS 인 GitHub(또는 GitHub Enterprise Server)다.
	SCMPlatformGitHub SCMPlatform = "github"
	// SCMPlatformGitea 는 스택 안에 설치되는 Gitea 다. GitHub 과 달리 주소가
	// 클러스터 내부 서비스 DNS 이고 조직도 우리가 만든다.
	SCMPlatformGitea SCMPlatform = "gitea"
)

type SCMProject

type SCMProject struct {
	ID            string `json:"id"`
	Name          string `json:"name"`
	FullPath      string `json:"full_path"`
	WebURL        string `json:"web_url"`
	HTTPCloneURL  string `json:"http_clone_url"`
	RegistryURL   string `json:"registry_url"`
	DefaultBranch string `json:"default_branch"`
	// Created 는 이번 호출이 저장소를 새로 만들었는지다.
	//
	// 스캐폴딩을 덮어쓸지 판단하는 데 쓴다. CommitFiles 는 upsert 라 이미 쓰이던
	// 저장소에 다시 커밋하면 CI 가 갱신해 둔 이미지 태그가 초기값으로 되돌아가고,
	// 사용자가 고친 Dockerfile·워크플로·매니페스트도 함께 사라진다.
	Created bool `json:"created"`
}

SCMProject 는 프로비저닝된 프로젝트다.

RegistryURL 은 이 프로젝트에 종속된 컨테이너 레지스트리 경로다. GitLab 의 Container/Package Registry 는 프로젝트 단위로 존재하므로, 공용 베이스 이미지를 두려면 그것을 소유할 프로젝트가 반드시 필요하다.

type SCMProvisioner

type SCMProvisioner interface {
	EnsureGroup(ctx context.Context, spec GroupSpec) (*SCMGroup, error)
	EnsureProject(ctx context.Context, spec ProjectSpec) (*SCMProject, error)
	// CommitFiles 는 파일이 이미 있으면 갱신한다(upsert).
	CommitFiles(ctx context.Context, projectID string, spec CommitSpec) error
	// DeleteProject 는 저장소를 지운다. 이미 없으면 성공으로 본다.
	//
	// 되돌릴 수 없다. 사용자가 명시적으로 요청했을 때만 호출해야 하며,
	// 파이프라인 삭제의 기본 동작이어서는 안 된다 — 소스 코드는 사용자 자산이다.
	DeleteProject(ctx context.Context, projectID string) error
}

SCMProvisioner 는 소스 저장소 플랫폼에 그룹·프로젝트·파일을 만든다.

Ensure* 는 멱등하다 — 이미 있으면 조회 결과를 그대로 돌려준다. 스택 설치는 여러 번 재시도될 수 있으므로 생성 API 를 그대로 노출하지 않는다.

type SCMTokenIssuer

type SCMTokenIssuer interface {
	EnsureToken(ctx context.Context, spec SCMTokenSpec) (string, error)
}

SCMTokenIssuer 는 SCM 플랫폼의 API 토큰을 확보한다.

GitLab 은 PAT 값을 생성 시점에만 돌려주므로, 발급한 값을 시크릿 저장소에 보관하고 이후에는 그것을 재사용한다. 저장된 값이 없을 때만 새로 발급한다.

type SCMTokenSpec

type SCMTokenSpec struct {
	// StackID 는 시크릿 저장소를 고르는 키다.
	//
	// OpenBao 는 스택마다 배포되므로 전역 주소가 하나일 수 없다.
	// 스택 범위로 접근해야 해당 스택의 OpenBao 에 저장된다.
	StackID string
	// ClusterID 는 kubeconfig 를 찾는 키다.
	ClusterID string
	// Namespace 는 SCM(GitLab)이 설치된 네임스페이스다.
	Namespace string
	// OrgID / Env 는 시크릿 저장 경로를 구성한다.
	OrgID string
	Env   string
	// Force 가 true 면 저장된 토큰을 무시하고 새로 발급한다.
	// 토큰이 만료되거나 폐기돼 401 을 받은 호출자가 쓴다.
	Force bool
}

SCMTokenSpec 은 SCM API 토큰 확보 요청이다.

type SCMWebhookProvisioner

type SCMWebhookProvisioner interface {
	EnsureWebhook(ctx context.Context, projectID, targetURL, secret string) error
}

SCMWebhookProvisioner 는 저장소에 webhook 을 건다.

Jenkins multibranch job 은 스스로 폴링하지 않는 한 새 커밋을 모른다. 폴링은 지연이 크고 리포가 늘수록 부하가 커지므로 push webhook 을 건다.

type ScanPolicyPublisher added in v0.6.0

type ScanPolicyPublisher interface {
	PublishScanPolicy(ctx context.Context, apps []string, vars []ProjectVariable) map[string]error
}

ScanPolicyPublisher 는 스캔 정책을 스택의 파이프라인들이 읽는 자리에 싣는다.

자리는 CI 마다 다르다 — GitLab·GitHub 은 앱 프로젝트의 변수, Jenkins 는 스택 네임스페이스의 ConfigMap 하나. 유스케이스가 그 차이를 알지 않도록 번들이 CI 에 맞는 구현을 쥔다. 결과는 앱마다 돌려준다 — 일부만 실패해도 어느 파이프라인이 옛 정책으로 도는지 알려야 한다.

type ScanPolicyRepository added in v0.6.0

type ScanPolicyRepository interface {
	// Get 은 저장된 정책이다. 저장한 적 없으면 found=false 다 — 기본값을 지어
	// 돌려주면 호출부가 운영자가 고른 값인지 구분할 수 없다.
	Get(ctx context.Context, stackID string) (policy domain.ScanPolicy, found bool, err error)
	// Upsert 는 스택의 정책을 저장하거나 덮어쓴다.
	Upsert(ctx context.Context, stackID string, policy domain.ScanPolicy, updatedBy string) error
}

ScanPolicyRepository 는 스택별 이미지 스캔 정책을 보관한다. cicd 모듈이 소유한다.

type ScanSource added in v0.6.0

type ScanSource string

ScanSource 는 이 이미지의 스캔 결과를 어디서 얻을지다.

const (
	// ScanSourceCentral 은 스택에 설치된 Trivy 서버로 스캔하는 것이다.
	ScanSourceCentral ScanSource = "central"
	// ScanSourceRegistry 는 레지스트리가 이미 가진 결과를 읽는 것이다.
	// 스캔을 한 번 더 돌리지 않는다.
	ScanSourceRegistry ScanSource = "registry"
	// ScanSourceNone 은 이 스택에서 이미지를 스캔할 수단이 없다는 뜻이다.
	//
	// 스캔을 건너뛰라는 뜻이 아니다 — 스캔 단계를 켤 수 없다는 뜻이고,
	// 호출부는 이것을 침묵이 아니라 거부로 다뤄야 한다.
	ScanSourceNone ScanSource = "none"
)

func ScanSourceFor added in v0.6.0

func ScanSourceFor(stack *StackSummary, kind RegistryKind) ScanSource

ScanSourceFor 는 스택 구성과 레지스트리 종류로 스캔 소스를 고른다.

스택에 스캐너가 설치돼 있으면 그것을 쓴다. 없으면 레지스트리 자체 기능으로 떨어지는데, 2026-09 기준 그것이 있는 것은 Harbor 뿐이다 — GitLab Container Registry 의 Container Scanning 은 CI 잡이지 레지스트리 기능이 아니고, Nexus 는 OSS 에 스캐너가 없으며(Sonatype IQ 는 상용), GHCR 은 네이티브 스캔이 없다. 없는 쪽에 스캐너를 꽂을 확장점도 없다.

Harbor 가 있는 스택에 스캐너를 또 깔 필요는 없다. 그 판단을 여기서 한다.

type SecretScanResultRepository added in v0.6.0

type SecretScanResultRepository interface {
	// Upsert 는 같은 ID 면 덮어쓴다.
	Upsert(ctx context.Context, result *domain.SecretScanResult) error
	// ListByPipelineID 는 최신 결과부터 돌려준다.
	ListByPipelineID(ctx context.Context, pipelineID string) ([]*domain.SecretScanResult, error)
	// Delete 는 기록을 지운다. 없으면 오류가 아니다.
	Delete(ctx context.Context, id string) error
}

SecretScanResultRepository 는 시크릿 탐지 결과 요약을 보관한다(Security Dashboard 설계 §8 G2). 비밀값은 받지도 두지도 않는다 — 도메인 타입에 그 자리가 없다.

type SourceArchive added in v0.6.0

type SourceArchive interface {
	// Fetch 는 repoURL 저장소의 commit 시점 파일을 돌려준다. 경로는 저장소 루트 기준이다.
	Fetch(ctx context.Context, repoURL, commit string) ([]CommitFile, error)
	// Supports 는 그 주소를 가져올 수 있는 모양인지 본다(네트워크를 쓰지 않는다). 템플릿을 저장할 때
	// 부른다 — 저장은 되는데 파이프라인을 만들 때마다 실패하는 템플릿이 생기지 않게.
	Supports(repoURL string) error
}

SourceArchive 는 외부 저장소의 특정 커밋 파일을 가져온다.

이력 없이 그 커밋의 파일만 돌려준다. 앱 저장소의 첫 커밋으로 그대로 올린다 — fork 는 같은 호스트 안에서만 되고(GitHub → 스택 GitLab 불가), 공개 저장소의 fork 는 private 로 둘 수 없다.

type StackReader

type StackReader interface {
	// GetStackSummary retrieves minimal stack information by ID.
	// Returns nil and no error if the stack does not exist.
	GetStackSummary(ctx context.Context, stackID string) (*StackSummary, error)
}

StackReader provides read-only access to Stack context data. This is a Port (in Hexagonal Architecture terms) that the CI/CD context uses to validate cross-context references without importing the Stack domain directly.

type StackSummary

type StackSummary struct {
	ID        string `json:"id"`
	OrgID     string `json:"org_id"`
	ClusterID string `json:"cluster_id"`
	State     string `json:"state"` // "completed", "failed", etc.

	// Name 은 스택 이름이다. 스택이 만든 게이트웨이 이름이 여기서 나온다
	// (`<이름>-gateway`). 도메인에서 유추하면 존재하지 않는 이름이 만들어진다.
	Name string `json:"name"`

	// Namespace 는 스택이 설치된 네임스페이스다. GitLab/Argo CD 의 클러스터 내
	// 주소를 만들 때 쓴다.
	Namespace string `json:"namespace"`
	// SourceRepository / ContainerRegistry 는 스택이 고른 도구 이름이다
	// (예: "GitLab CE", "GitLab Registry", "Harbor").
	// 이미지 저장 위치를 결정하는 근거가 된다.
	SourceRepository  string `json:"source_repository"`
	ContainerRegistry string `json:"container_registry"`
	// AccessDomain 은 스택의 외부 접근 도메인이다. 비어 있을 수 있다.
	AccessDomain string `json:"access_domain"`
	// OTLPEndpoint 는 스택 OpenTelemetry Collector 의 클러스터 내 주소다
	// (host:port). 수집기를 고르지 않은 스택에서는 비어 있다.
	//
	// 배포되는 앱에 넣어 줄 값이라 CI/CD 가 알아야 하는데, 릴리스명·차트명으로
	// 주소를 조립하는 규칙은 Stack 컨텍스트의 것이다. 그래서 조립된 결과만
	// 받아 온다 — 여기서 규칙을 흉내 내면 차트가 바뀔 때 조용히 어긋난다.
	OTLPEndpoint string `json:"otlp_endpoint"`

	// ImageScannerEndpoint 는 스택 Trivy 서버의 클러스터 내 주소다
	// (http://host:port). 스캐너를 고르지 않은 스택에서는 비어 있다.
	//
	// OTLPEndpoint 와 같은 이유로 조립된 결과만 받는다 — 릴리스명·포트로
	// 주소를 만드는 규칙은 shared 가 소유하고, 여기서 흉내 내면 차트가 바뀔 때
	// 조용히 어긋난다.
	ImageScannerEndpoint string `json:"image_scanner_endpoint"`

	// SASTServerEndpoint 는 스택 SonarQube 의 클러스터 내 주소다(http://host:port).
	// SonarQube 를 고르지 않았거나 외부 SonarQube 를 쓰는 스택에서는 비어 있다.
	SASTServerEndpoint string `json:"sast_server_endpoint"`

	// DependencyScanEndpoint 는 의존성 스캔(SCA)이 붙을 스택 Trivy 서버 주소다(nullus-plan#79).
	// 의존성 스캔을 고르지 않은 스택에서는 비어 있다 — 이미지 스캐너와 같은 서버라 값이 같을 수 있지만,
	// 둘은 따로 고르는 단계라 칸도 따로 둔다.
	DependencyScanEndpoint string `json:"dependency_scan_endpoint"`
}

StackSummary contains the minimal information the CI/CD module needs from the Stack context. This avoids importing stack/domain directly, keeping the Bounded Context boundary intact.

type SyncablePipelineLister added in v0.6.0

type SyncablePipelineLister interface {
	ListWithStack(ctx context.Context) ([]*domain.Pipeline, error)
}

SyncablePipelineLister 는 스택에 묶인 파이프라인을 조직과 무관하게 돌려준다. 실행 기록 주기 동기화가 쓴다 — 그것은 조직 경계와 무관한 일이다.

type WorkloadDeleter

type WorkloadDeleter interface {
	DeleteByLabel(ctx context.Context, kubeconfig []byte, namespace, selector string) error
}

WorkloadDeleter 는 라벨로 찾은 워크로드를 지운다.

Argo CD 로 배포한 앱은 Application 을 지우면 컨트롤러가 함께 걷어내지만, 매니페스트를 직접 적용하는 경로는 지워 줄 주체가 없다. 그쪽 워크로드는 이것으로 정리한다.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL