- ProviderAdapter 인터페이스를 정의하고 AWS/GitLab provider 구현체를 추가한다 - CRD model에 ProviderRef 및 status 필드를 추가하여 provider 관리를 표준화한다 - ControlPlane에 provider registry를 통합하여 runtime/periodic event routing을 provider별로 분기한다 - Router에 routeByProvider 메서드를 추가하여 provider별 event 라우팅을 지원한다 - Event 시스템에 outbound event 전송 기능을 추가한다 - 관련 테스트를 추가하여 provider별 동작을 검증한다
247 lines
8 KiB
Go
247 lines
8 KiB
Go
package provider
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.toki-labs.com/toki/gito/services/core/internal/core"
|
|
)
|
|
|
|
type ProviderID string
|
|
|
|
const (
|
|
ProviderGitHub ProviderID = "github"
|
|
ProviderGitLab ProviderID = "gitlab"
|
|
ProviderGitea ProviderID = "gitea"
|
|
ProviderForgejo ProviderID = "forgejo"
|
|
ProviderPlane ProviderID = "plane"
|
|
ProviderJira ProviderID = "jira"
|
|
)
|
|
|
|
type Capability uint64
|
|
|
|
const (
|
|
CapabilityChangeRequest Capability = 1 << iota
|
|
CapabilityWebhook
|
|
CapabilityChecks
|
|
CapabilityComment
|
|
)
|
|
|
|
func (c Capability) Has(flag Capability) bool {
|
|
return flag != 0 && c&flag == flag
|
|
}
|
|
|
|
type Config struct {
|
|
ID ProviderID
|
|
Endpoint string
|
|
CredentialRef string
|
|
Capabilities Capability
|
|
CreatedAt time.Time
|
|
UpdatedAt time.Time
|
|
}
|
|
|
|
func (c Config) Supports(flag Capability) bool {
|
|
return c.Capabilities.Has(flag)
|
|
}
|
|
|
|
func (c Config) Normalized() Config {
|
|
c.ID = ProviderID(strings.ToLower(strings.TrimSpace(string(c.ID))))
|
|
c.Endpoint = strings.TrimSpace(c.Endpoint)
|
|
c.CredentialRef = strings.TrimSpace(c.CredentialRef)
|
|
return c
|
|
}
|
|
|
|
type ChangeRequestAdapter interface {
|
|
CreateChangeRequest(ctx context.Context, input core.ChangeRequestCreate) (core.ChangeRequest, error)
|
|
UpdateChangeRequest(ctx context.Context, id string, input core.ChangeRequestUpdate) (core.ChangeRequest, error)
|
|
CommentChangeRequest(ctx context.Context, id string, input core.ChangeRequestComment) error
|
|
RequestChangeRequestReview(ctx context.Context, id string, input core.ChangeRequestReviewRequest) error
|
|
MergeChangeRequest(ctx context.Context, id string, input core.ChangeRequestMerge) (core.ChangeRequest, error)
|
|
CloseChangeRequest(ctx context.Context, id string, input core.ChangeRequestClose) (core.ChangeRequest, error)
|
|
}
|
|
|
|
type ChangeRequestMappingCandidate struct {
|
|
Provider ProviderID
|
|
ProviderObject string
|
|
Fields []ChangeRequestFieldMapping
|
|
States []ChangeRequestStateMapping
|
|
Actions []core.ChangeRequestAction
|
|
ExtensionFields []ChangeRequestExtensionField
|
|
}
|
|
|
|
type ChangeRequestFieldMapping struct {
|
|
Target string
|
|
Source string
|
|
Notes string
|
|
}
|
|
|
|
type ChangeRequestStateMapping struct {
|
|
State core.ChangeRequestState
|
|
Source string
|
|
Notes string
|
|
}
|
|
|
|
// ChangeRequestExtensionField describes provider-owned metadata that adapters
|
|
// keep outside core.ChangeRequest until a later milestone promotes it.
|
|
type ChangeRequestExtensionField struct {
|
|
Name string
|
|
Source string
|
|
Notes string
|
|
}
|
|
|
|
func ChangeRequestMappingCandidates() []ChangeRequestMappingCandidate {
|
|
return []ChangeRequestMappingCandidate{
|
|
GitHubChangeRequestMapping(),
|
|
GitLabChangeRequestMapping(),
|
|
GiteaChangeRequestMapping(),
|
|
}
|
|
}
|
|
|
|
func GitHubChangeRequestMapping() ChangeRequestMappingCandidate {
|
|
return ChangeRequestMappingCandidate{
|
|
Provider: ProviderGitHub,
|
|
ProviderObject: "pull_request",
|
|
Fields: []ChangeRequestFieldMapping{
|
|
{Target: "provider", Source: string(ProviderGitHub)},
|
|
{Target: "external_id", Source: "number/pull_number"},
|
|
{Target: "repo_id", Source: "Gito repo binding"},
|
|
{Target: "source_branch", Source: "head"},
|
|
{Target: "target_branch", Source: "base"},
|
|
{Target: "title", Source: "title"},
|
|
{Target: "body", Source: "body"},
|
|
{Target: "draft", Source: "draft"},
|
|
{Target: "state", Source: "state + merged check"},
|
|
},
|
|
States: []ChangeRequestStateMapping{
|
|
{State: core.ChangeRequestOpen, Source: "state=open"},
|
|
{State: core.ChangeRequestMerged, Source: "merged=true or merge endpoint success"},
|
|
{State: core.ChangeRequestClosed, Source: "state=closed and merged=false"},
|
|
},
|
|
Actions: changeRequestActions(),
|
|
ExtensionFields: []ChangeRequestExtensionField{
|
|
{Name: "head_repo", Source: "head_repo", Notes: "cross-repository create metadata"},
|
|
{Name: "maintainer_can_modify", Source: "maintainer_can_modify"},
|
|
{Name: "merge_method", Source: "merge_method"},
|
|
{Name: "issue", Source: "issue", Notes: "issue conversion and issue-only fields stay adapter-owned"},
|
|
},
|
|
}
|
|
}
|
|
|
|
func GitLabChangeRequestMapping() ChangeRequestMappingCandidate {
|
|
return ChangeRequestMappingCandidate{
|
|
Provider: ProviderGitLab,
|
|
ProviderObject: "merge_request",
|
|
Fields: []ChangeRequestFieldMapping{
|
|
{Target: "provider", Source: string(ProviderGitLab)},
|
|
{Target: "external_id", Source: "iid"},
|
|
{Target: "repo_id", Source: "Gito repo binding"},
|
|
{Target: "source_branch", Source: "source_branch"},
|
|
{Target: "target_branch", Source: "target_branch"},
|
|
{Target: "title", Source: "title"},
|
|
{Target: "body", Source: "description"},
|
|
{Target: "draft", Source: "draft"},
|
|
{Target: "state", Source: "state"},
|
|
},
|
|
States: []ChangeRequestStateMapping{
|
|
{State: core.ChangeRequestOpen, Source: "state=opened"},
|
|
{State: core.ChangeRequestMerged, Source: "state=merged"},
|
|
{State: core.ChangeRequestClosed, Source: "state=closed"},
|
|
},
|
|
Actions: changeRequestActions(),
|
|
ExtensionFields: []ChangeRequestExtensionField{
|
|
{Name: "project_id", Source: "project_id/source_project_id/target_project_id"},
|
|
{Name: "detailed_merge_status", Source: "detailed_merge_status"},
|
|
{Name: "squash_on_merge", Source: "squash_on_merge"},
|
|
{Name: "merge_after", Source: "merge_after"},
|
|
{Name: "blocking_discussions_resolved", Source: "blocking_discussions_resolved"},
|
|
},
|
|
}
|
|
}
|
|
|
|
func GiteaChangeRequestMapping() ChangeRequestMappingCandidate {
|
|
return ChangeRequestMappingCandidate{
|
|
Provider: ProviderGitea,
|
|
ProviderObject: "pull_request",
|
|
Fields: []ChangeRequestFieldMapping{
|
|
{Target: "provider", Source: string(ProviderGitea)},
|
|
{Target: "external_id", Source: "number"},
|
|
{Target: "repo_id", Source: "Gito repo binding"},
|
|
{Target: "source_branch", Source: "head"},
|
|
{Target: "target_branch", Source: "base"},
|
|
{Target: "title", Source: "title"},
|
|
{Target: "body", Source: "body"},
|
|
{Target: "draft", Source: "draft"},
|
|
{Target: "state", Source: "state + merged"},
|
|
},
|
|
States: []ChangeRequestStateMapping{
|
|
{State: core.ChangeRequestOpen, Source: "state=open"},
|
|
{State: core.ChangeRequestMerged, Source: "merged=true"},
|
|
{State: core.ChangeRequestClosed, Source: "state=closed and merged=false"},
|
|
},
|
|
Actions: changeRequestActions(),
|
|
ExtensionFields: []ChangeRequestExtensionField{
|
|
{Name: "mergeable", Source: "mergeable"},
|
|
{Name: "allow_maintainer_edit", Source: "allow_maintainer_edit"},
|
|
{Name: "merge_style", Source: "merge option style"},
|
|
{Name: "deadline", Source: "due_date"},
|
|
{Name: "requested_reviewers", Source: "requested_reviewers/requested_reviewers_teams"},
|
|
},
|
|
}
|
|
}
|
|
|
|
func changeRequestActions() []core.ChangeRequestAction {
|
|
return []core.ChangeRequestAction{
|
|
core.ChangeRequestActionCreate,
|
|
core.ChangeRequestActionUpdate,
|
|
core.ChangeRequestActionComment,
|
|
core.ChangeRequestActionRequestReview,
|
|
core.ChangeRequestActionMerge,
|
|
core.ChangeRequestActionClose,
|
|
}
|
|
}
|
|
|
|
type WebhookEvent struct {
|
|
Provider string
|
|
EventType string
|
|
ExternalID string
|
|
Payload []byte
|
|
}
|
|
|
|
// WebhookRequest represents a provider webhook with all metadata needed
|
|
// for signature verification and normalization.
|
|
type WebhookRequest struct {
|
|
Provider ProviderID
|
|
EventType string
|
|
ExternalID string
|
|
Payload []byte
|
|
Headers map[string][]string
|
|
Query map[string][]string
|
|
ReceivedAt time.Time
|
|
}
|
|
|
|
// WebhookCandidate represents a provider-neutral normalization candidate
|
|
// that contains minimal metadata without requiring provider secrets.
|
|
type WebhookCandidate struct {
|
|
Type string
|
|
Provider ProviderID
|
|
ExternalID string
|
|
Revision *core.RevisionEvent
|
|
}
|
|
|
|
// WebhookVerifier defines the interface for verifying webhook signatures.
|
|
type WebhookVerifier interface {
|
|
VerifyWebhook(ctx context.Context, req WebhookRequest) error
|
|
}
|
|
|
|
// WebhookNormalizer defines the interface for normalizing webhook events
|
|
// into provider-neutral candidates.
|
|
type WebhookNormalizer interface {
|
|
NormalizeWebhook(ctx context.Context, req WebhookRequest) ([]WebhookCandidate, error)
|
|
}
|
|
|
|
// WebhookAdapter combines verification and normalization capabilities.
|
|
type WebhookAdapter interface {
|
|
WebhookVerifier
|
|
WebhookNormalizer
|
|
}
|