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  }