diff --git a/cmd/app/initilization.go b/cmd/app/initilization.go
index 380dcd7a..23e671a5 100644
--- a/cmd/app/initilization.go
+++ b/cmd/app/initilization.go
@@ -95,7 +95,7 @@ func newRepositoryHost(host string, client *github.Client, httpClient *http.Clie
if host == "github.com" {
rawHost = "raw.githubusercontent.com"
}
- return githubhttpcache.NewGHC(host, client, httpClient, &osshim.OsShim{}, []string{host, rawHost}, localMappings, options)
+ return githubhttpcache.NewGHC(host, client, client.Repositories, client.Git, httpClient, &osshim.OsShim{}, []string{host, rawHost}, localMappings, options)
}
// NewReactor creates a Reactor from Options
diff --git a/pkg/manifest/manifest.go b/pkg/manifest/manifest.go
index 562034ae..6e460069 100644
--- a/pkg/manifest/manifest.go
+++ b/pkg/manifest/manifest.go
@@ -38,11 +38,6 @@ func processManifest(f nodeTransformation, node *Node, parent *Node, manifest *N
}
func loadManifestStructure(node *Node, parent *Node, manifest *Node, r resourcehandlers.Registry) error {
- var (
- err error
- content string
- newManifest string
- )
if node.Manifest == "" {
return nil
}
@@ -50,7 +45,8 @@ func loadManifestStructure(node *Node, parent *Node, manifest *Node, r resourceh
if err != nil {
return err
}
- if newManifest, err = fs.ToAbsLink(manifest.Manifest, node.Manifest); err != nil {
+ newManifest, err := fs.ToAbsLink(manifest.Manifest, node.Manifest)
+ if err != nil {
return fmt.Errorf("can't build manifest node %s absolute URL : %w ", node.Manifest, err)
}
node.Manifest = newManifest
@@ -58,7 +54,8 @@ func loadManifestStructure(node *Node, parent *Node, manifest *Node, r resourceh
if err != nil {
return err
}
- if content, err = fs.ManifestFromURL(node.Manifest); err != nil {
+ content, err := fs.ManifestFromURL(node.Manifest)
+ if err != nil {
return fmt.Errorf("can't get manifest file content : %w", err)
}
if err = yaml.Unmarshal([]byte(content), node); err != nil {
diff --git a/pkg/osfakes/httpclient/httpclientfakes/fake_client.go b/pkg/osfakes/httpclient/httpclientfakes/fake_client.go
index 526dc431..532de8ad 100644
--- a/pkg/osfakes/httpclient/httpclientfakes/fake_client.go
+++ b/pkg/osfakes/httpclient/httpclientfakes/fake_client.go
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: 2021 SAP SE or an SAP affiliate company and Gardener contributors
+// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors
//
// SPDX-License-Identifier: Apache-2.0
// Code generated by counterfeiter. DO NOT EDIT.
diff --git a/pkg/osfakes/osshim/osshimfakes/fake_os.go b/pkg/osfakes/osshim/osshimfakes/fake_os.go
index cf9cf12e..fd04c6b6 100644
--- a/pkg/osfakes/osshim/osshimfakes/fake_os.go
+++ b/pkg/osfakes/osshim/osshimfakes/fake_os.go
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: 2021 SAP SE or an SAP affiliate company and Gardener contributors
+// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors
//
// SPDX-License-Identifier: Apache-2.0
// Code generated by counterfeiter. DO NOT EDIT.
diff --git a/pkg/readers/link/resource_link.go b/pkg/readers/link/resource_link.go
index c8e7449f..3462410c 100644
--- a/pkg/readers/link/resource_link.go
+++ b/pkg/readers/link/resource_link.go
@@ -7,7 +7,6 @@ package link
import (
"fmt"
"net/url"
- "path"
"regexp"
)
@@ -100,13 +99,3 @@ func (r *Resource) GetRepoURL() string {
func (r *Resource) GetRawURL() string {
return fmt.Sprintf("https://%s/%s/%s/raw/%s/%s", r.Host, r.Owner, r.Repo, r.Ref, r.Path)
}
-
-// GetResourceName returns the name of the resource (including extension), if resource path is empty returns '.'
-func (r *Resource) GetResourceName() string {
- return path.Base(r.Path)
-}
-
-// GetResourceExt returns the resource name extension, empty string if when no extension exists
-func (r *Resource) GetResourceExt() string {
- return path.Ext(r.Path)
-}
diff --git a/pkg/readers/repositoryhosts/githubhttpcache/github_http_cache.go b/pkg/readers/repositoryhosts/githubhttpcache/github_http_cache.go
index 44d30d06..3b8d7560 100644
--- a/pkg/readers/repositoryhosts/githubhttpcache/github_http_cache.go
+++ b/pkg/readers/repositoryhosts/githubhttpcache/github_http_cache.go
@@ -4,6 +4,8 @@
package githubhttpcache
+//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate -header ../../../../license_prefix.txt
+
import (
"context"
"encoding/base64"
@@ -15,6 +17,7 @@ import (
"os"
"path"
"path/filepath"
+ "slices"
"sort"
"strings"
"sync"
@@ -32,8 +35,10 @@ import (
// GHC implements repositoryhosts.RepositoryHost interface using GitHub manifestadapter with transport level persistent cache.
type GHC struct {
hostName string
- client *github.Client
- httpClient *http.Client
+ client httpclient.Client
+ git Git
+ rateLimit RateLimitSource
+ repositories Repositories
os osshim.Os
acceptedHosts []string
localMappings map[string]string
@@ -45,12 +50,38 @@ type GHC struct {
options manifest.ParsingOptions
}
+//counterfeiter:generate . RateLimitSource
+
+// RateLimitSource is an interface needed for faking
+type RateLimitSource interface {
+ RateLimits(ctx context.Context) (*github.RateLimits, *github.Response, error)
+}
+
+//counterfeiter:generate . Repositories
+
+// Repositories is an interface needed for faking
+type Repositories interface {
+ ListCommits(ctx context.Context, owner, repo string, opts *github.CommitsListOptions) ([]*github.RepositoryCommit, *github.Response, error)
+ GetContents(ctx context.Context, owner, repo, path string, opts *github.RepositoryContentGetOptions) (fileContent *github.RepositoryContent, directoryContent []*github.RepositoryContent, resp *github.Response, err error)
+ Get(ctx context.Context, owner, repo string) (*github.Repository, *github.Response, error)
+}
+
+//counterfeiter:generate . Git
+
+// Git is an interface needed for faking
+type Git interface {
+ GetBlobRaw(ctx context.Context, owner, repo, sha string) ([]byte, *github.Response, error)
+ GetTree(ctx context.Context, owner string, repo string, sha string, recursive bool) (*github.Tree, *github.Response, error)
+}
+
// NewGHC creates new GHC resource handler
-func NewGHC(hostName string, client *github.Client, httpClient *http.Client, os osshim.Os, acceptedHosts []string, localMappings map[string]string, options manifest.ParsingOptions) repositoryhosts.RepositoryHost {
+func NewGHC(hostName string, rateLimit RateLimitSource, repositories Repositories, git Git, client httpclient.Client, os osshim.Os, acceptedHosts []string, localMappings map[string]string, options manifest.ParsingOptions) repositoryhosts.RepositoryHost {
return &GHC{
hostName: hostName,
client: client,
- httpClient: httpClient,
+ git: git,
+ rateLimit: rateLimit,
+ repositories: repositories,
os: os,
acceptedHosts: acceptedHosts,
localMappings: localMappings,
@@ -80,8 +111,8 @@ type GitInfo struct {
//========================= manifest.FileSource ===================================================
// FileTreeFromURL implements manifest.FileSource#FileTreeFromURL
-func (p *GHC) FileTreeFromURL(url string) ([]string, error) {
- r, err := p.getResolvedResourceInfo(context.TODO(), url)
+func (p *GHC) FileTreeFromURL(URL string) ([]string, error) {
+ r, err := p.getResolvedResourceInfo(context.TODO(), URL)
if err != nil {
return nil, err
}
@@ -94,12 +125,20 @@ func (p *GHC) FileTreeFromURL(url string) ([]string, error) {
if local := p.checkForLocalMapping(r); len(local) > 0 {
return p.readLocalFileTree(*r, local), nil
}
- t, err := p.getTree(context.TODO(), r, true)
+ sha := fmt.Sprintf("%s:%s", r.Ref, r.Path)
+ sha = url.PathEscape(sha)
+ tree, resp, err := p.git.GetTree(context.TODO(), r.Owner, r.Repo, sha, true)
+ if resp != nil && resp.StatusCode == http.StatusNotFound {
+ return nil, repositoryhosts.ErrResourceNotFound(r.String())
+ }
+ if resp != nil && resp.StatusCode >= 400 {
+ return nil, fmt.Errorf("reading tree %s fails with HTTP status: %d", r.String(), resp.StatusCode)
+ }
if err != nil {
return nil, err
}
res := []string{}
- for _, e := range t.Entries {
+ for _, e := range tree.Entries {
extracted := false
ePath := strings.TrimPrefix(*e.Path, "/")
for _, extractedFormat := range p.options.ExtractedFilesFormats {
@@ -114,7 +153,6 @@ func (p *GHC) FileTreeFromURL(url string) ([]string, error) {
continue
}
res = append(res, ePath)
-
}
return res, nil
}
@@ -125,13 +163,12 @@ func (p *GHC) ManifestFromURL(url string) (string, error) {
if err != nil {
return "", err
}
- content, err := p.Read(context.TODO(), r.GetResourceURL())
+ content, err := p.Read(context.TODO(), r.String())
return string(content), err
}
// ToAbsLink implements manifest.FileSource#ToAbsLink
func (p *GHC) ToAbsLink(source, link string) (string, error) {
-
r, err := p.getResolvedResourceInfo(context.TODO(), source)
if err != nil {
return link, err
@@ -141,11 +178,40 @@ func (p *GHC) ToAbsLink(source, link string) (string, error) {
if err != nil {
return link, err
}
- link = l.GetResourceURL()
+ link = l.String()
+ }
+ l, err := url.Parse(strings.TrimSuffix(link, "/"))
+ if err != nil {
+ return link, err
+ }
+ if l.IsAbs() {
+ return link, nil // already absolute
}
- res, err := p.buildAbsLink(r, link)
+ // build URL based on source path
+ u, err := url.Parse("/" + r.Path)
+ if err != nil {
+ return link, err
+ }
+ if u, err = u.Parse(l.Path); err != nil {
+ return link, err
+ }
+ // determine the type of the resource: (blob|tree)
+ var tp string
+ if tp, err = p.determineLinkType(r, u); err != nil {
+ return tp, err
+ }
+ res, err := url.Parse(r.URL.String())
+ if err != nil {
+ return "", err
+ }
+ // set path
+ res.Path = fmt.Sprintf("/%s/%s/%s/%s%s", r.Owner, r.Repo, tp, r.Ref, u.Path)
+ // set query & fragment
+ res.ForceQuery = l.ForceQuery
+ res.RawQuery = l.RawQuery
+ res.Fragment = l.Fragment
- return res, err
+ return res.String(), nil
}
//========================= repositoryhosts.RepositoryHost ===================================================
@@ -157,12 +223,12 @@ func (p *GHC) Name() string {
// Accept implements the repositoryhosts.RepositoryHost#Accept
func (p *GHC) Accept(uri string) bool {
- r, err := link.NewResource(uri)
- if err != nil || r.URL.Scheme != "https" {
+ r, err := url.Parse(uri)
+ if err != nil || r.Scheme != "https" {
return false
}
for _, h := range p.acceptedHosts {
- if h == r.URL.Host {
+ if h == r.Host {
return true
}
}
@@ -181,7 +247,46 @@ func (p *GHC) Read(ctx context.Context, uri string) ([]byte, error) {
if local := p.checkForLocalMapping(r); len(local) > 0 {
return p.readLocalFile(ctx, r, local)
}
- return p.readFile(ctx, r)
+ // read using GitService and file URL -> file SHA mapping
+ if SHA, ok := p.getFileSHA(r.String()); ok {
+ raw, resp, err := p.git.GetBlobRaw(ctx, r.Owner, r.Repo, SHA)
+ if err != nil {
+ if resp != nil && resp.StatusCode == http.StatusNotFound {
+ return nil, repositoryhosts.ErrResourceNotFound(r.String())
+ }
+ return nil, err
+ }
+ if resp != nil && resp.StatusCode >= 400 {
+ return nil, fmt.Errorf("reading blob %s fails with HTTP status: %d", r.String(), resp.StatusCode)
+ }
+ return raw, nil
+ }
+ // read using RepositoriesService.DownloadContents for non-markdown and non-manifest files - 2 manifestadapter calls
+ opt := &github.RepositoryContentGetOptions{Ref: r.Ref}
+ if !strings.HasSuffix(strings.ToLower(r.Path), ".md") && !strings.HasSuffix(strings.ToLower(r.Path), ".yaml") {
+ return p.downloadContent(ctx, opt, r)
+ }
+ // read using RepositoriesService.GetContents for markdowns and module manifests - 1 manifestadapter call
+ fc, _, resp, err := p.repositories.GetContents(ctx, r.Owner, r.Repo, r.Path, opt)
+ if err != nil {
+ if resp != nil && resp.StatusCode == http.StatusNotFound {
+ return nil, repositoryhosts.ErrResourceNotFound(r.String())
+ }
+ if resp != nil && resp.StatusCode == http.StatusForbidden {
+ // if file is bigger than 1 MB -> content should be downloaded
+ // it makes two additional manifestadapter cals, but it's unlikely to have large manifest.yaml
+ return p.downloadContent(ctx, opt, r)
+ }
+ return nil, err
+ }
+ if resp != nil && resp.StatusCode >= 400 {
+ return nil, fmt.Errorf("reading blob %s fails with HTTP status: %d", r.String(), resp.StatusCode)
+ }
+ cnt, err := base64.StdEncoding.DecodeString(*fc.Content)
+ if err != nil {
+ return nil, err
+ }
+ return cnt, nil
}
// ReadGitInfo implements the repositoryhosts.RepositoryHost#ReadGitInfo
@@ -196,30 +301,23 @@ func (p *GHC) ReadGitInfo(ctx context.Context, uri string) ([]byte, error) {
}
var commits []*github.RepositoryCommit
var resp *github.Response
- if commits, resp, err = p.client.Repositories.ListCommits(ctx, r.Owner, r.Repo, opts); err != nil {
+ if commits, resp, err = p.repositories.ListCommits(ctx, r.Owner, r.Repo, opts); err != nil {
return nil, err
}
if resp != nil && resp.StatusCode >= 400 {
return nil, fmt.Errorf("list commits for %s fails with HTTP status: %d", r.String(), resp.StatusCode)
}
- var blob []byte
- if commits != nil {
- gitInfo := transform(commits)
- if gitInfo == nil {
- return nil, nil
- }
-
- if len(r.Ref) > 0 {
- gitInfo.SHAAlias = &r.Ref
- }
- if len(r.Path) > 0 {
- gitInfo.Path = &r.Path
- }
- if blob, err = marshallGitInfo(gitInfo); err != nil {
- return nil, err
- }
+ gitInfo := transform(commits)
+ if gitInfo == nil {
+ return nil, nil
+ }
+ if len(r.Ref) > 0 {
+ gitInfo.SHAAlias = &r.Ref
}
- return blob, nil
+ if len(r.Path) > 0 {
+ gitInfo.Path = &r.Path
+ }
+ return json.MarshalIndent(gitInfo, "", " ")
}
// GetRawFormatLink implements the repositoryhosts.RepositoryHost#GetRawFormatLink
@@ -236,12 +334,12 @@ func (p *GHC) GetRawFormatLink(absLink string) (string, error) {
// GetClient implements the repositoryhosts.RepositoryHost#GetClient
func (p *GHC) GetClient() httpclient.Client {
- return p.httpClient
+ return p.client
}
// GetRateLimit implements the repositoryhosts.RepositoryHost#GetRateLimit
func (p *GHC) GetRateLimit(ctx context.Context) (int, int, time.Time, error) {
- r, _, err := p.client.RateLimits(ctx)
+ r, _, err := p.rateLimit.RateLimits(ctx)
if err != nil {
return -1, -1, time.Now(), err
}
@@ -261,51 +359,6 @@ func (p *GHC) checkForLocalMapping(r *link.Resource) string {
return p.localMappings[key+"/"]
}
-// readFile reads a file from GitHub
-func (p *GHC) readFile(ctx context.Context, r *link.Resource) ([]byte, error) {
- var cnt []byte
- // read using GitService and file URL -> file SHA mapping
- if SHA, ok := p.getFileSHA(r.String()); ok {
- raw, resp, err := p.client.Git.GetBlobRaw(ctx, r.Owner, r.Repo, SHA)
- if err != nil {
- if resp != nil && resp.StatusCode == http.StatusNotFound {
- return nil, repositoryhosts.ErrResourceNotFound(r.String())
- }
- return nil, err
- }
- if resp != nil && resp.StatusCode >= 400 {
- return nil, fmt.Errorf("reading blob %s fails with HTTP status: %d", r.String(), resp.StatusCode)
- }
- return raw, nil
- }
- // read using RepositoriesService.DownloadContents for non-markdown and non-manifest files - 2 manifestadapter calls
- opt := &github.RepositoryContentGetOptions{Ref: r.Ref}
- if !strings.HasSuffix(strings.ToLower(r.Path), ".md") && !strings.HasSuffix(strings.ToLower(r.Path), ".yaml") {
- return p.downloadContent(ctx, opt, r)
- }
- // read using RepositoriesService.GetContents for markdowns and module manifests - 1 manifestadapter call
- fc, _, resp, err := p.client.Repositories.GetContents(ctx, r.Owner, r.Repo, r.Path, opt)
- if err != nil {
- if resp != nil && resp.StatusCode == http.StatusNotFound {
- return nil, repositoryhosts.ErrResourceNotFound(r.String())
- }
- if resp != nil && resp.StatusCode == http.StatusForbidden {
- // if file is bigger than 1 MB -> content should be downloaded
- // it makes two additional manifestadapter cals, but it's unlikely to have large manifest.yaml
- return p.downloadContent(ctx, opt, r)
- }
- return nil, err
- }
- if resp != nil && resp.StatusCode >= 400 {
- return nil, fmt.Errorf("reading blob %s fails with HTTP status: %d", r.String(), resp.StatusCode)
- }
- cnt, err = base64.StdEncoding.DecodeString(*fc.Content)
- if err != nil {
- return nil, err
- }
- return cnt, nil
-}
-
// readLocalFile reads a file from FS
func (p *GHC) readLocalFile(_ context.Context, r *link.Resource, localPath string) ([]byte, error) {
fn := filepath.Join(localPath, r.Path)
@@ -347,8 +400,7 @@ func (p *GHC) downloadContent(ctx context.Context, opt *github.RepositoryContent
if contents.SHA == nil || *contents.SHA == "" {
return nil, fmt.Errorf("no SHA found for %s", r.String())
}
- var cnt []byte
- cnt, resp, err = p.client.Git.GetBlobRaw(ctx, r.Owner, r.Repo, *contents.SHA)
+ cnt, resp, err := p.git.GetBlobRaw(ctx, r.Owner, r.Repo, *contents.SHA)
if err != nil {
if resp != nil && resp.StatusCode == http.StatusNotFound {
return nil, repositoryhosts.ErrResourceNotFound(r.String())
@@ -369,74 +421,16 @@ func (p *GHC) downloadContent(ctx context.Context, opt *github.RepositoryContent
func (p *GHC) getDirContents(ctx context.Context, owner, repo, path string, opts *github.RepositoryContentGetOptions) (dc []*github.RepositoryContent, resp *github.Response, err error) {
p.muxCnt.Lock()
defer p.muxCnt.Unlock()
- _, dc, resp, err = p.client.Repositories.GetContents(ctx, owner, repo, path, opts)
+ _, dc, resp, err = p.repositories.GetContents(ctx, owner, repo, path, opts)
return
}
-// getTree returns subtree with root r#Path
-func (p *GHC) getTree(ctx context.Context, r *link.Resource, recursive bool) (*github.Tree, error) {
- sha := fmt.Sprintf("%s:%s", r.Ref, r.Path)
- sha = url.PathEscape(sha)
- gitTree, resp, err := p.client.Git.GetTree(ctx, r.Owner, r.Repo, sha, recursive)
- if err != nil {
- if resp != nil && resp.StatusCode == http.StatusNotFound {
- return nil, repositoryhosts.ErrResourceNotFound(r.String())
- }
- return nil, err
- }
- if resp != nil && resp.StatusCode >= 400 {
- return nil, fmt.Errorf("reading tree %s fails with HTTP status: %d", r.String(), resp.StatusCode)
- }
- return gitTree, nil
-}
-
-// buildAbsLink builds absolute link if is relative using