[GIT] Add Support for Other Git Clients (#171)

This commit is contained in:
Johannes Batzill 2023-01-10 14:35:09 -08:00 committed by GitHub
parent afd86bacb0
commit a426cdd69b
11 changed files with 98 additions and 25 deletions

View File

@ -137,7 +137,7 @@ func initSystem(ctx context.Context, config *types.Config) (*system, error) {
apiHandler := router.ProvideAPIHandler(config, authenticator, accountClient, controller, spaceController, repoController, pullreqController, webhookController, githookController)
gitHandler := router.ProvideGitHandler(config, provider, repoStore, authenticator, authorizer, gitrpcInterface)
webHandler := router2.ProvideWebHandler(config)
routerRouter := router2.ProvideRouter(apiHandler, gitHandler, webHandler)
routerRouter := router2.ProvideRouter(config, apiHandler, gitHandler, webHandler)
serverServer := server.ProvideServer(config, routerRouter)
serverConfig := ProvideGitRPCServerConfig(config)
server3, err := server2.ProvideServer(serverConfig)

View File

@ -100,7 +100,7 @@ func initSystem(ctx context.Context, config *types.Config) (*system, error) {
apiHandler := router.ProvideAPIHandler(config, authenticator, repoController, spaceController, pullreqController, webhookController, githookController, serviceaccountController, controller)
gitHandler := router.ProvideGitHandler(config, provider, repoStore, authenticator, authorizer, gitrpcInterface)
webHandler := router.ProvideWebHandler(config)
routerRouter := router.ProvideRouter(apiHandler, gitHandler, webHandler)
routerRouter := router.ProvideRouter(config, apiHandler, gitHandler, webHandler)
serverServer := server.ProvideServer(config, routerRouter)
serverConfig := ProvideGitRPCServerConfig(config)
server3, err := server2.ProvideServer(serverConfig)

View File

@ -66,7 +66,7 @@ func CreateRPCWriteParams(ctx context.Context, urlProvider *url.Provider,
// generate envars (add everything githook CLI needs for execution)
envVars, err := githook.GenerateEnvironmentVariables(&githook.Payload{
BaseURL: urlProvider.GetAPIBaseURLInternal(),
APIBaseURL: urlProvider.GetAPIBaseURLInternal(),
RepoID: repo.ID,
PrincipalID: session.Principal.ID,
RequestID: requestID,

View File

@ -36,7 +36,7 @@ func NewCLI() (*CLI, error) {
payload: payload,
client: &client{
httpClient: http.DefaultClient,
baseURL: payload.BaseURL,
baseURL: payload.APIBaseURL,
requestPreparation: func(r *http.Request) *http.Request {
// TODO: reference single constant (together with gitness middleware)
r.Header.Add("X-Request-Id", payload.RequestID)

View File

@ -53,7 +53,7 @@ func (c *client) PostReceive(ctx context.Context,
// githook executes the requested githook type using the provided input.
func (c *client) githook(ctx context.Context, githookType string, in interface{}) (*githook.ServerHookOutput, error) {
uri := fmt.Sprintf("%s/api/v1/internal/git-hooks/%s", c.baseURL, githookType)
uri := fmt.Sprintf("%s/v1/internal/git-hooks/%s", c.baseURL, githookType)
bodyBytes, err := json.Marshal(in)
if err != nil {
return nil, fmt.Errorf("failed to serialize input: %w", err)

View File

@ -21,7 +21,7 @@ const (
// Payload defines the Payload the githook binary is initiated with when executing the git hooks.
type Payload struct {
BaseURL string
APIBaseURL string
RepoID int64
PrincipalID int64
RequestID string
@ -83,7 +83,7 @@ func validatePayload(payload *Payload) error {
if payload == nil {
return errors.New("payload is empty")
}
if payload.BaseURL == "" {
if payload.APIBaseURL == "" {
return errors.New("payload doesn't contain a base url")
}
if payload.PrincipalID <= 0 {

View File

@ -19,14 +19,18 @@ import (
)
const (
APIMount = "/api"
gitUserAgentPrefix = "git/"
APIMount = "/api"
GitMount = "/git"
)
type Router struct {
api APIHandler
git GitHandler
web WebHandler
// gitHost describes the optional host via which git traffic is identified.
// Note: always stored as lowercase.
gitHost string
}
// NewRouter returns a new http.Handler that routes traffic
@ -35,11 +39,14 @@ func NewRouter(
api APIHandler,
git GitHandler,
web WebHandler,
gitHost string,
) *Router {
return &Router{
api: api,
git: git,
web: web,
gitHost: strings.ToLower(gitHost),
}
}
@ -57,15 +64,19 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
* 1. GIT
*
* All Git originating traffic starts with "/space1/space2/repo.git".
* This can collide with other API endpoints and thus has to be checked first.
* To avoid any false positives, we use the user-agent header to identify git agents.
*/
ua := req.Header.Get("user-agent")
if strings.HasPrefix(ua, gitUserAgentPrefix) {
if r.isGitTraffic(req) {
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
return c.Str("http.handler", "git")
})
// remove matched prefix to simplify API handlers (only if it's there)
if err = stripPrefix(GitMount, req); err != nil {
hlog.FromRequest(req).Err(err).Msgf("Failed striping of prefix for git request.")
render.InternalError(w)
return
}
r.git.ServeHTTP(w, req)
return
}
@ -75,8 +86,7 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
*
* All Rest API calls start with "/api/", and thus can be uniquely identified.
*/
p := req.URL.Path
if strings.HasPrefix(p, APIMount) {
if r.isAPITraffic(req) {
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
return c.Str("http.handler", "api")
})
@ -104,7 +114,40 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
r.web.ServeHTTP(w, req)
}
// stripPrefix removes the prefix from the request path (expected to be there).
func stripPrefix(prefix string, r *http.Request) error {
return request.ReplacePrefix(r, r.URL.Path[:len(prefix)], "")
// stripPrefix removes the prefix from the request path (or noop if it's not there).
func stripPrefix(prefix string, req *http.Request) error {
p := req.URL.Path
if !strings.HasPrefix(p, prefix) {
return nil
}
return request.ReplacePrefix(req, req.URL.Path[:len(prefix)], "")
}
// isGitTraffic returns true iff the request is identified as part of the git http protocol.
func (r *Router) isGitTraffic(req *http.Request) bool {
// git traffic is always reachable via the git mounting path.
p := req.URL.Path
if strings.HasPrefix(p, GitMount) {
return true
}
// otherwise check if the request came in via the configured git host (if enabled)
if len(r.gitHost) > 0 {
// cut (optional) port off the host
h, _, _ := strings.Cut(req.Host, ":")
// check if request host matches the configured git host (case insensitive)
if r.gitHost == strings.ToLower(h) {
return true
}
}
// otherwise we don't treat it as git traffic
return false
}
// isAPITraffic returns true iff the request is identified as part of our rest API.
func (r *Router) isAPITraffic(req *http.Request) bool {
p := req.URL.Path
return strings.HasPrefix(p, APIMount)
}

View File

@ -31,11 +31,13 @@ var WireSet = wire.NewSet(
)
func ProvideRouter(
config *types.Config,
api APIHandler,
git GitHandler,
web WebHandler,
) *Router {
return NewRouter(api, git, web)
return NewRouter(api, git, web,
config.Server.HTTP.GitHost)
}
func ProvideGitHandler(

View File

@ -14,9 +14,12 @@ import (
// Provider provides the URLs of the gitness system.
type Provider struct {
// apiURLRaw stores the raw URL the api endpoints are reachable at publicly.
// NOTE: url is guaranteed to not have any trailing '/'.
apiURLRaw string
// apiURLInternalRaw stores the raw URL the api endpoints are reachable at internally.
// NOTE: no need for internal services to go via public route.
// apiURLInternalRaw stores the raw URL the api endpoints are reachable at internally
// (no need for internal services to go via public route).
// NOTE: url is guaranteed to not have any trailing '/'.
apiURLInternalRaw string
// gitURL stores the URL the git endpoints are available at.
@ -25,6 +28,12 @@ type Provider struct {
}
func NewProvider(apiURLRaw string, apiURLInternalRaw, gitURLRaw string) (*Provider, error) {
// remove trailing '/' to make usage easier
apiURLRaw = strings.TrimRight(apiURLRaw, "/")
apiURLInternalRaw = strings.TrimRight(apiURLInternalRaw, "/")
gitURLRaw = strings.TrimRight(gitURLRaw, "/")
// parse gitURL
gitURL, err := url.Parse(gitURLRaw)
if err != nil {
return nil, fmt.Errorf("provided gitURLRaw '%s' is invalid: %w", gitURLRaw, err)
@ -38,16 +47,19 @@ func NewProvider(apiURLRaw string, apiURLInternalRaw, gitURLRaw string) (*Provid
}
// GetAPIBaseURL returns the publicly reachable base url of the api server.
// NOTE: url is guaranteed to not have any trailing '/'.
func (p *Provider) GetAPIBaseURL() string {
return p.apiURLRaw
}
// GetAPIBaseURLInternal returns the internally reachable base url of the api server.
// NOTE: url is guaranteed to not have any trailing '/'.
func (p *Provider) GetAPIBaseURLInternal() string {
return p.apiURLInternalRaw
}
// GenerateRepoCloneURL generates the public git clone URL for the provided repo path.
// NOTE: url is guaranteed to not have any trailing '/'.
func (p *Provider) GenerateRepoCloneURL(repoPath string) string {
repoPath = path.Clean(repoPath)
if !strings.HasSuffix(repoPath, ".git") {

View File

@ -12,7 +12,7 @@ import (
)
var (
illegalRootSpaceNames = []string{"api"}
illegalRootSpaceNames = []string{"api", "git"}
ErrRootSpaceNameNotAllowed = &ValidationError{
fmt.Sprintf("The following names are not allowed for a root space: %v", illegalRootSpaceNames),

View File

@ -18,9 +18,23 @@ type Config struct {
// URL defines the URLs via which the different parts of the service are reachable by.
URL struct {
Git string `envconfig:"GITNESS_URL_GIT" default:"http://localhost:3000"`
API string `envconfig:"GITNESS_URL_API" default:"http://localhost:3000"`
APIInternal string `envconfig:"GITNESS_URL_API_INTERNAL" default:"http://localhost:3000"`
// Git defines the external URL via which the GIT API is reachable.
// NOTE: for routing to work properly, the request path & hostname reaching gitness
// have to statisfy at least one of the following two conditions:
// - Path ends with `/git`
// - Hostname matches Config.Server.HTTP.GitHost
// (this could be after proxy path / header rewrite).
Git string `envconfig:"GITNESS_URL_GIT" default:"http://localhost:3000/git"`
// API defines the external URL via which the rest API is reachable.
// NOTE: for routing to work properly, the request path reaching gitness has to end with `/api`
// (this could be after proxy path rewrite).
API string `envconfig:"GITNESS_URL_API" default:"http://localhost:3000/api"`
// APIInternal defines the internal URL via which the rest API is reachable.
// NOTE: for routing to work properly, the request path reaching gitness has to end with `/api`
// (this could be after proxy path rewrite).
APIInternal string `envconfig:"GITNESS_URL_API_INTERNAL" default:"http://localhost:3000/api"`
}
// Git defines the git configuration parameters
@ -38,6 +52,8 @@ type Config struct {
Bind string `envconfig:"GITNESS_HTTP_BIND" default:":3000"`
Proto string `envconfig:"GITNESS_HTTP_PROTO"`
Host string `envconfig:"GITNESS_HTTP_HOST"`
// GitHost is the host used to identify git traffic (OPTIONAL).
GitHost string `envconfig:"GITNESS_HTTP_GIT_HOST" default:"git.localhost"`
}
// GRPC defines the grpc configuration parameters