github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/pkg/git/v2/remote.go (about) 1 /* 2 Copyright 2019 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package git 18 19 import ( 20 "errors" 21 "fmt" 22 "net/url" 23 "path" 24 25 gerritsource "sigs.k8s.io/prow/pkg/gerrit/source" 26 ) 27 28 // RemoteResolverFactory knows how to construct remote resolvers for 29 // authoritative central remotes (to pull from) and publish remotes 30 // (to push to) for a repository. These resolvers are called at run-time 31 // to determine remotes for git commands. 32 type RemoteResolverFactory interface { 33 // CentralRemote returns a resolver for a remote server with an 34 // authoritative version of the repository. This type of remote 35 // is useful for fetching refs and cloning. 36 CentralRemote(org, repo string) RemoteResolver 37 // PublishRemote returns a resolver for a remote server with a 38 // personal fork of the central repository. This type of remote is most 39 // useful for publishing local changes. 40 PublishRemote(org, centralRepo string) ForkRemoteResolver 41 } 42 43 // RemoteResolver knows how to construct a remote URL for git calls 44 type RemoteResolver func() (string, error) 45 46 // ForkRemoteResolver knows how to construct a remote URL for git calls 47 // It accepts a fork name since this may be different than the parent 48 // repo name. If the forkName is "", the parent repo name is assumed. 49 type ForkRemoteResolver func(forkName string) (string, error) 50 51 // LoginGetter fetches a GitHub login on-demand 52 type LoginGetter func() (login string, err error) 53 54 // TokenGetter fetches a GitHub OAuth token on-demand 55 type TokenGetter func(org string) (string, error) 56 57 type sshRemoteResolverFactory struct { 58 host string 59 username LoginGetter 60 } 61 62 // CentralRemote creates a remote resolver that refers to an authoritative remote 63 // for the repository. 64 func (f *sshRemoteResolverFactory) CentralRemote(org, repo string) RemoteResolver { 65 remote := fmt.Sprintf("git@%s:%s/%s.git", f.host, org, repo) 66 return func() (string, error) { 67 return remote, nil 68 } 69 } 70 71 // PublishRemote creates a remote resolver that refers to a user's remote 72 // for the repository that can be published to. 73 func (f *sshRemoteResolverFactory) PublishRemote(_, centralRepo string) ForkRemoteResolver { 74 return func(forkName string) (string, error) { 75 repo := centralRepo 76 if forkName != "" { 77 repo = forkName 78 } 79 org, err := f.username() 80 if err != nil { 81 return "", err 82 } 83 return fmt.Sprintf("git@%s:%s/%s.git", f.host, org, repo), nil 84 } 85 } 86 87 type httpResolverFactory struct { 88 // Whether to use HTTP. 89 http bool 90 host string 91 // Optional, either both or none must be set 92 username LoginGetter 93 token TokenGetter 94 } 95 96 // CentralRemote creates a remote resolver that refers to an authoritative remote 97 // for the repository. 98 func (f *httpResolverFactory) CentralRemote(org, repo string) RemoteResolver { 99 return func() (string, error) { 100 return f.resolve(org, repo) 101 } 102 } 103 104 // PublishRemote creates a remote resolver that refers to a user's remote 105 // for the repository that can be published to. 106 func (f *httpResolverFactory) PublishRemote(_, centralRepo string) ForkRemoteResolver { 107 return func(forkName string) (string, error) { 108 // For the publsh remote we use: 109 // - the user login rather than the central org 110 // - the forkName rather than the central repo name, if specified. 111 repo := centralRepo 112 if forkName != "" { 113 repo = forkName 114 } 115 if f.username == nil { 116 return "", errors.New("username not configured, no publish repo available") 117 } 118 org, err := f.username() 119 if err != nil { 120 return "", fmt.Errorf("could not resolve username: %w", err) 121 } 122 remote, err := f.resolve(org, repo) 123 if err != nil { 124 err = fmt.Errorf("could not resolve remote: %w", err) 125 } 126 return remote, err 127 } 128 } 129 130 // resolve builds the URL string for the given org/repo remote identifier, it 131 // respects the configured scheme, and the dynamic username and credentials. 132 func (f *httpResolverFactory) resolve(org, repo string) (string, error) { 133 scheme := "https" 134 if f.http { 135 scheme = "http" 136 } 137 remote := &url.URL{Scheme: scheme, Host: f.host, Path: fmt.Sprintf("%s/%s", org, repo)} 138 139 if f.username != nil { 140 name, err := f.username() 141 if err != nil { 142 return "", fmt.Errorf("could not resolve username: %w", err) 143 } 144 token, err := f.token(org) 145 if err != nil { 146 return "", fmt.Errorf("could not resolve token: %w", err) 147 } 148 remote.User = url.UserPassword(name, token) 149 } 150 151 return remote.String(), nil 152 } 153 154 // pathResolverFactory generates resolvers for local path-based repositories, 155 // used in local integration testing only 156 type pathResolverFactory struct { 157 baseDir string 158 } 159 160 // CentralRemote creates a remote resolver that refers to an authoritative remote 161 // for the repository. 162 func (f *pathResolverFactory) CentralRemote(org, repo string) RemoteResolver { 163 return func() (string, error) { 164 return path.Join(f.baseDir, org, repo), nil 165 } 166 } 167 168 // PublishRemote creates a remote resolver that refers to a user's remote 169 // for the repository that can be published to. 170 func (f *pathResolverFactory) PublishRemote(org, centralRepo string) ForkRemoteResolver { 171 return func(_ string) (string, error) { 172 return path.Join(f.baseDir, org, centralRepo), nil 173 } 174 } 175 176 // gerritResolverFactory is meant to be used by Gerrit only. It's so different 177 // from GitHub that there is no way any of the remotes logic can be shared 178 // between these two providers. The resulting CentralRemote and PublishRemote 179 // are both the clone URI. 180 type gerritResolverFactory struct{} 181 182 func (f *gerritResolverFactory) CentralRemote(org, repo string) RemoteResolver { 183 return func() (string, error) { 184 return gerritsource.CloneURIFromOrgRepo(org, repo), nil 185 } 186 } 187 188 func (f *gerritResolverFactory) PublishRemote(org, repo string) ForkRemoteResolver { 189 return func(_ string) (string, error) { 190 return gerritsource.CloneURIFromOrgRepo(org, repo), nil 191 } 192 }