github.com/sdboyer/gps@v0.16.3/maybe_source.go (about) 1 package gps 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "net/url" 8 "path/filepath" 9 "strings" 10 11 "github.com/Masterminds/vcs" 12 ) 13 14 // A maybeSource represents a set of information that, given some 15 // typically-expensive network effort, could be transformed into a proper source. 16 // 17 // Wrapping these up as their own type achieves two goals: 18 // 19 // * Allows control over when deduction logic triggers network activity 20 // * Makes it easy to attempt multiple URLs for a given import path 21 type maybeSource interface { 22 try(ctx context.Context, cachedir string, c singleSourceCache, superv *supervisor) (source, sourceState, error) 23 getURL() string 24 } 25 26 type maybeSources []maybeSource 27 28 func (mbs maybeSources) try(ctx context.Context, cachedir string, c singleSourceCache, superv *supervisor) (source, sourceState, error) { 29 var e sourceFailures 30 for _, mb := range mbs { 31 src, state, err := mb.try(ctx, cachedir, c, superv) 32 if err == nil { 33 return src, state, nil 34 } 35 e = append(e, sourceSetupFailure{ 36 ident: mb.getURL(), 37 err: err, 38 }) 39 } 40 return nil, 0, e 41 } 42 43 // This really isn't generally intended to be used - the interface is for 44 // maybeSources to be able to interrogate its members, not other things to 45 // interrogate a maybeSources. 46 func (mbs maybeSources) getURL() string { 47 strslice := make([]string, 0, len(mbs)) 48 for _, mb := range mbs { 49 strslice = append(strslice, mb.getURL()) 50 } 51 return strings.Join(strslice, "\n") 52 } 53 54 type sourceSetupFailure struct { 55 ident string 56 err error 57 } 58 59 func (e sourceSetupFailure) Error() string { 60 return fmt.Sprintf("failed to set up %q, error %s", e.ident, e.err.Error()) 61 } 62 63 type sourceFailures []sourceSetupFailure 64 65 func (sf sourceFailures) Error() string { 66 var buf bytes.Buffer 67 fmt.Fprintf(&buf, "no valid source could be created:") 68 for _, e := range sf { 69 fmt.Fprintf(&buf, "\n\t%s", e.Error()) 70 } 71 72 return buf.String() 73 } 74 75 type maybeGitSource struct { 76 url *url.URL 77 } 78 79 func (m maybeGitSource) try(ctx context.Context, cachedir string, c singleSourceCache, superv *supervisor) (source, sourceState, error) { 80 ustr := m.url.String() 81 path := filepath.Join(cachedir, "sources", sanitizer.Replace(ustr)) 82 83 r, err := vcs.NewGitRepo(ustr, path) 84 if err != nil { 85 return nil, 0, unwrapVcsErr(err) 86 } 87 88 src := &gitSource{ 89 baseVCSSource: baseVCSSource{ 90 repo: &gitRepo{r}, 91 }, 92 } 93 94 // Pinging invokes the same action as calling listVersions, so just do that. 95 var vl []PairedVersion 96 err = superv.do(ctx, "git:lv:maybe", ctListVersions, func(ctx context.Context) (err error) { 97 if vl, err = src.listVersions(ctx); err != nil { 98 return fmt.Errorf("remote repository at %s does not exist, or is inaccessible", ustr) 99 } 100 return nil 101 }) 102 if err != nil { 103 return nil, 0, err 104 } 105 106 c.storeVersionMap(vl, true) 107 state := sourceIsSetUp | sourceExistsUpstream | sourceHasLatestVersionList 108 109 if r.CheckLocal() { 110 state |= sourceExistsLocally 111 } 112 113 return src, state, nil 114 } 115 116 func (m maybeGitSource) getURL() string { 117 return m.url.String() 118 } 119 120 type maybeGopkginSource struct { 121 // the original gopkg.in import path. this is used to create the on-disk 122 // location to avoid duplicate resource management - e.g., if instances of 123 // a gopkg.in project are accessed via different schemes, or if the 124 // underlying github repository is accessed directly. 125 opath string 126 // the actual upstream URL - always github 127 url *url.URL 128 // the major version to apply for filtering 129 major uint64 130 } 131 132 func (m maybeGopkginSource) try(ctx context.Context, cachedir string, c singleSourceCache, superv *supervisor) (source, sourceState, error) { 133 // We don't actually need a fully consistent transform into the on-disk path 134 // - just something that's unique to the particular gopkg.in domain context. 135 // So, it's OK to just dumb-join the scheme with the path. 136 path := filepath.Join(cachedir, "sources", sanitizer.Replace(m.url.Scheme+"/"+m.opath)) 137 ustr := m.url.String() 138 139 r, err := vcs.NewGitRepo(ustr, path) 140 if err != nil { 141 return nil, 0, unwrapVcsErr(err) 142 } 143 144 src := &gopkginSource{ 145 gitSource: gitSource{ 146 baseVCSSource: baseVCSSource{ 147 repo: &gitRepo{r}, 148 }, 149 }, 150 major: m.major, 151 } 152 153 var vl []PairedVersion 154 err = superv.do(ctx, "git:lv:maybe", ctListVersions, func(ctx context.Context) (err error) { 155 if vl, err = src.listVersions(ctx); err != nil { 156 return fmt.Errorf("remote repository at %s does not exist, or is inaccessible", ustr) 157 } 158 return nil 159 }) 160 if err != nil { 161 return nil, 0, err 162 } 163 164 c.storeVersionMap(vl, true) 165 state := sourceIsSetUp | sourceExistsUpstream | sourceHasLatestVersionList 166 167 if r.CheckLocal() { 168 state |= sourceExistsLocally 169 } 170 171 return src, state, nil 172 } 173 174 func (m maybeGopkginSource) getURL() string { 175 return m.opath 176 } 177 178 type maybeBzrSource struct { 179 url *url.URL 180 } 181 182 func (m maybeBzrSource) try(ctx context.Context, cachedir string, c singleSourceCache, superv *supervisor) (source, sourceState, error) { 183 ustr := m.url.String() 184 path := filepath.Join(cachedir, "sources", sanitizer.Replace(ustr)) 185 186 r, err := vcs.NewBzrRepo(ustr, path) 187 if err != nil { 188 return nil, 0, unwrapVcsErr(err) 189 } 190 191 err = superv.do(ctx, "bzr:ping", ctSourcePing, func(ctx context.Context) error { 192 if !r.Ping() { 193 return fmt.Errorf("remote repository at %s does not exist, or is inaccessible", ustr) 194 } 195 return nil 196 }) 197 if err != nil { 198 return nil, 0, err 199 } 200 201 state := sourceIsSetUp | sourceExistsUpstream 202 if r.CheckLocal() { 203 state |= sourceExistsLocally 204 } 205 206 src := &bzrSource{ 207 baseVCSSource: baseVCSSource{ 208 repo: &bzrRepo{r}, 209 }, 210 } 211 212 return src, state, nil 213 } 214 215 func (m maybeBzrSource) getURL() string { 216 return m.url.String() 217 } 218 219 type maybeHgSource struct { 220 url *url.URL 221 } 222 223 func (m maybeHgSource) try(ctx context.Context, cachedir string, c singleSourceCache, superv *supervisor) (source, sourceState, error) { 224 ustr := m.url.String() 225 path := filepath.Join(cachedir, "sources", sanitizer.Replace(ustr)) 226 227 r, err := vcs.NewHgRepo(ustr, path) 228 if err != nil { 229 return nil, 0, unwrapVcsErr(err) 230 } 231 232 err = superv.do(ctx, "hg:ping", ctSourcePing, func(ctx context.Context) error { 233 if !r.Ping() { 234 return fmt.Errorf("remote repository at %s does not exist, or is inaccessible", ustr) 235 } 236 return nil 237 }) 238 if err != nil { 239 return nil, 0, err 240 } 241 242 state := sourceIsSetUp | sourceExistsUpstream 243 if r.CheckLocal() { 244 state |= sourceExistsLocally 245 } 246 247 src := &hgSource{ 248 baseVCSSource: baseVCSSource{ 249 repo: &hgRepo{r}, 250 }, 251 } 252 253 return src, state, nil 254 } 255 256 func (m maybeHgSource) getURL() string { 257 return m.url.String() 258 }