repospec.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. /*
  2. Copyright 2018 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package git
  14. import (
  15. "fmt"
  16. "path/filepath"
  17. "strings"
  18. "sigs.k8s.io/kustomize/pkg/fs"
  19. )
  20. // Used as a temporary non-empty occupant of the cloneDir
  21. // field, as something distinguishable from the empty string
  22. // in various outputs (especially tests). Not using an
  23. // actual directory name here, as that's a temporary directory
  24. // with a unique name that isn't created until clone time.
  25. const notCloned = fs.ConfirmedDir("/notCloned")
  26. // RepoSpec specifies a git repository and a branch and path therein.
  27. type RepoSpec struct {
  28. // Raw, original spec, used to look for cycles.
  29. // TODO(monopole): Drop raw, use processed fields instead.
  30. raw string
  31. // Host, e.g. github.com
  32. host string
  33. // orgRepo name (organization/repoName),
  34. // e.g. kubernetes-sigs/kustomize
  35. orgRepo string
  36. // ConfirmedDir where the orgRepo is cloned to.
  37. cloneDir fs.ConfirmedDir
  38. // Relative path in the repository, and in the cloneDir,
  39. // to a Kustomization.
  40. path string
  41. // Branch or tag reference.
  42. ref string
  43. }
  44. // CloneSpec returns a string suitable for "git clone {spec}".
  45. func (x *RepoSpec) CloneSpec() string {
  46. if isAzureHost(x.host) || isAWSHost(x.host) {
  47. return x.host + x.orgRepo
  48. }
  49. return x.host + x.orgRepo + gitSuffix
  50. }
  51. func (x *RepoSpec) CloneDir() fs.ConfirmedDir {
  52. return x.cloneDir
  53. }
  54. func (x *RepoSpec) Raw() string {
  55. return x.raw
  56. }
  57. func (x *RepoSpec) AbsPath() string {
  58. return x.cloneDir.Join(x.path)
  59. }
  60. func (x *RepoSpec) Cleaner(fSys fs.FileSystem) func() error {
  61. return func() error { return fSys.RemoveAll(x.cloneDir.String()) }
  62. }
  63. // From strings like git@github.com:someOrg/someRepo.git or
  64. // https://github.com/someOrg/someRepo?ref=someHash, extract
  65. // the parts.
  66. func NewRepoSpecFromUrl(n string) (*RepoSpec, error) {
  67. if filepath.IsAbs(n) {
  68. return nil, fmt.Errorf("uri looks like abs path: %s", n)
  69. }
  70. host, orgRepo, path, gitRef := parseGithubUrl(n)
  71. if orgRepo == "" {
  72. return nil, fmt.Errorf("url lacks orgRepo: %s", n)
  73. }
  74. if host == "" {
  75. return nil, fmt.Errorf("url lacks host: %s", n)
  76. }
  77. return &RepoSpec{
  78. raw: n, host: host, orgRepo: orgRepo,
  79. cloneDir: notCloned, path: path, ref: gitRef}, nil
  80. }
  81. const (
  82. refQuery = "?ref="
  83. gitSuffix = ".git"
  84. )
  85. // From strings like git@github.com:someOrg/someRepo.git or
  86. // https://github.com/someOrg/someRepo?ref=someHash, extract
  87. // the parts.
  88. func parseGithubUrl(n string) (
  89. host string, orgRepo string, path string, gitRef string) {
  90. host, n = parseHostSpec(n)
  91. if strings.Contains(n, gitSuffix) {
  92. index := strings.Index(n, gitSuffix)
  93. orgRepo = n[0:index]
  94. n = n[index+len(gitSuffix):]
  95. path, gitRef = peelQuery(n)
  96. return
  97. }
  98. i := strings.Index(n, "/")
  99. if i < 1 {
  100. return "", "", "", ""
  101. }
  102. j := strings.Index(n[i+1:], "/")
  103. if j >= 0 {
  104. j += i + 1
  105. orgRepo = n[:j]
  106. path, gitRef = peelQuery(n[j+1:])
  107. } else {
  108. path = ""
  109. orgRepo, gitRef = peelQuery(n)
  110. }
  111. return
  112. }
  113. func peelQuery(arg string) (string, string) {
  114. j := strings.Index(arg, refQuery)
  115. if j >= 0 {
  116. return arg[:j], arg[j+len(refQuery):]
  117. }
  118. return arg, ""
  119. }
  120. func parseHostSpec(n string) (string, string) {
  121. var host string
  122. // Start accumulating the host part.
  123. for _, p := range []string{
  124. // Order matters here.
  125. "git::", "gh:", "ssh://", "https://", "http://",
  126. "git@", "github.com:", "github.com/"} {
  127. if len(p) < len(n) && strings.ToLower(n[:len(p)]) == p {
  128. n = n[len(p):]
  129. host += p
  130. }
  131. }
  132. if host == "git@" {
  133. i := strings.Index(n, "/")
  134. if i > -1 {
  135. host += n[:i+1]
  136. n = n[i+1:]
  137. } else {
  138. i = strings.Index(n, ":")
  139. if i > -1 {
  140. host += n[:i+1]
  141. n = n[i+1:]
  142. }
  143. }
  144. return host, n
  145. }
  146. // If host is a http(s) or ssh URL, grab the domain part.
  147. for _, p := range []string{
  148. "ssh://", "https://", "http://"} {
  149. if strings.HasSuffix(host, p) {
  150. i := strings.Index(n, "/")
  151. if i > -1 {
  152. host = host + n[0:i+1]
  153. n = n[i+1:]
  154. }
  155. break
  156. }
  157. }
  158. return normalizeGitHostSpec(host), n
  159. }
  160. func normalizeGitHostSpec(host string) string {
  161. s := strings.ToLower(host)
  162. if strings.Contains(s, "github.com") {
  163. if strings.Contains(s, "git@") || strings.Contains(s, "ssh:") {
  164. host = "git@github.com:"
  165. } else {
  166. host = "https://github.com/"
  167. }
  168. }
  169. if strings.HasPrefix(s, "git::") {
  170. host = strings.TrimLeft(s, "git::")
  171. }
  172. return host
  173. }
  174. // The format of Azure repo URL is documented
  175. // https://docs.microsoft.com/en-us/azure/devops/repos/git/clone?view=vsts&tabs=visual-studio#clone_url
  176. func isAzureHost(host string) bool {
  177. return strings.Contains(host, "dev.azure.com") ||
  178. strings.Contains(host, "visualstudio.com")
  179. }
  180. // The format of AWS repo URL is documented
  181. // https://docs.aws.amazon.com/codecommit/latest/userguide/regions.html
  182. func isAWSHost(host string) bool {
  183. return strings.Contains(host, "amazonaws.com")
  184. }