Skip to content

Commit

Permalink
Allow using the --from to get template from a github release
Browse files Browse the repository at this point in the history
Currently, `clusterctl generate cluster`'s `--from` flag only support
yaml files from github code. Trying to get template from a rlease tag is
failing.

This PR adds the option to use the `--form` also for release assets.

Signed-off-by: Nahshon Unna-Tsameret <[email protected]>
  • Loading branch information
nunnatsa committed Jan 2, 2023
1 parent a1c2fa8 commit 39e99d3
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 11 deletions.
74 changes: 63 additions & 11 deletions cmd/clusterctl/client/cluster/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package cluster
import (
"context"
"encoding/base64"
"fmt"
"io"
"net/http"
"net/url"
Expand Down Expand Up @@ -180,37 +181,58 @@ func (t *templateClient) getGitHubFileContent(rURL *url.URL) ([]byte, error) {
urlSplit := strings.Split(strings.TrimPrefix(rURL.Path, "/"), "/")
if len(urlSplit) < 5 {
return nil, errors.Errorf(
"invalid GitHub url %q: a GitHub url should be in the form https://github.com/{owner}/{repository}/blob/{branch}/{path-to-file}", rURL,
"invalid GitHub url %q: a GitHub url should be in on of these the forms\n"+
"- https://github.com/{owner}/{repository}/blob/{branch}/{path-to-file}\n"+
"- https://github.com/{owner}/{repository}/releases/download/{tag}/{asset-file-name}", rURL,
)
}

// Extract all the info from url split.
owner := urlSplit[0]
repository := urlSplit[1]
branch := urlSplit[3]
path := strings.Join(urlSplit[4:], "/")
repo := urlSplit[1]
linkType := urlSplit[2]

// gets the GitHub client
client, err := t.gitHubClientFactory(t.configClient.Variables())
ghClient, err := t.gitHubClientFactory(t.configClient.Variables())
if err != nil {
return nil, err
}

// gets the file from GiHub
fileContent, _, _, err := client.Repositories.GetContents(context.TODO(), owner, repository, path, &github.RepositoryContentGetOptions{Ref: branch})
switch linkType {
case "blob": // get file from a code in a github repo
branch := urlSplit[3]
path := strings.Join(urlSplit[4:], "/")

return getGithubFileContentFromCode(ghClient, rURL.Path, owner, repo, path, branch)

case "releases": // get a github release asset
if urlSplit[3] != "download" {
break
}
tag := urlSplit[4]
assetName := urlSplit[5]

return getGithubAssetFromRelease(ghClient, rURL.Path, owner, repo, tag, assetName)
}

return nil, fmt.Errorf("unknown github URL: %v", rURL)
}

func getGithubFileContentFromCode(ghClient *github.Client, fullPath string, owner string, repo string, path string, branch string) ([]byte, error) {
fileContent, _, _, err := ghClient.Repositories.GetContents(ctx, owner, repo, path, &github.RepositoryContentGetOptions{Ref: branch})
if err != nil {
return nil, handleGithubErr(err, "failed to get %q", rURL.Path)
return nil, handleGithubErr(err, "failed to get %q", fullPath)
}
if fileContent == nil {
return nil, errors.Errorf("%q does not return a valid file content", rURL.Path)
return nil, errors.Errorf("%q does not return a valid file content", fullPath)
}
if fileContent.Encoding == nil || *fileContent.Encoding != "base64" {
return nil, errors.Errorf("invalid encoding detected for %q. Only base64 encoding supported", rURL.Path)
return nil, errors.Errorf("invalid encoding detected for %q. Only base64 encoding supported", fullPath)
}

content, err := base64.StdEncoding.DecodeString(*fileContent.Content)
if err != nil {
return nil, errors.Wrapf(err, "failed to decode file %q", rURL.Path)
return nil, errors.Wrapf(err, "failed to decode file %q", fullPath)
}
return content, nil
}
Expand Down Expand Up @@ -239,6 +261,36 @@ func (t *templateClient) getRawURLFileContent(rURL string) ([]byte, error) {
return content, nil
}

func getGithubAssetFromRelease(ghClient *github.Client, path string, owner string, repo string, tag string, assetName string) ([]byte, error) {
release, _, err := ghClient.Repositories.GetReleaseByTag(ctx, owner, repo, tag)
if err != nil {
return nil, handleGithubErr(err, "failed to get release '%s' from %s/%s repository", tag, owner, repo)
}

if release == nil {
return nil, fmt.Errorf("can't find release '%s' in %s/%s repository", tag, owner, repo)
}

var rc io.ReadCloser
for _, asset := range release.Assets {
if asset.GetName() == assetName {
rc, _, err = ghClient.Repositories.DownloadReleaseAsset(ctx, owner, repo, asset.GetID(), ghClient.Client())
if err != nil {
return nil, errors.Wrapf(err, "failed to download file %q", path)
}
break
}
}

if rc == nil {
return nil, fmt.Errorf("failed to download the file %q", path)
}

defer func() { _ = rc.Close() }()

return io.ReadAll(rc)
}

func getGitHubClient(configVariablesClient config.VariablesClient) (*github.Client, error) {
var authenticatingHTTPClient *http.Client
if token, err := configVariablesClient.Get(config.GitHubTokenVariable); err == nil {
Expand Down
76 changes: 76 additions & 0 deletions cmd/clusterctl/client/cluster/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,52 @@ func Test_templateClient_GetFromURL(t *testing.T) {
}`)
})

mux.HandleFunc("/repos/some-owner/some-repo/releases/tags/v1.0.0", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, `{
"tag_name": "v1.0.0",
"name": "v1.0.0",
"id": 12345678,
"url": "https://api.github.com/repos/some-owner/some-repo/releases/12345678",
"assets": [
{
"id": 87654321,
"name": "cluster-template.yaml"
}
]
}`)
})

mux.HandleFunc("/repos/some-owner/some-repo/releases/assets/87654321", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, template)
})

mux.HandleFunc("/repos/some-owner/some-repo/releases/tags/v2.0.0", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, `{
"tag_name": "v2.0.0",
"name": "v2.0.0",
"id": 12345678,
"url": "https://api.github.com/repos/some-owner/some-repo/releases/12345678",
"assets": [
{
"id": 22222222,
"name": "cluster-template.yaml"
}
]
}`)
})

// redirect asset
mux.HandleFunc("/repos/some-owner/some-repo/releases/assets/22222222", func(w http.ResponseWriter, r *http.Request) {
// add the "/api-v3" prefix to match the prefix of the fake github server
w.Header().Add("Location", "/api-v3/redirected/22222222")
w.WriteHeader(http.StatusFound)
})

// redirect location
mux.HandleFunc("/redirected/22222222", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, template)
})

path := filepath.Join(tmpDir, "cluster-template.yaml")
g.Expect(os.WriteFile(path, []byte(template), 0600)).To(Succeed())

Expand Down Expand Up @@ -388,6 +434,36 @@ func Test_templateClient_GetFromURL(t *testing.T) {
want: template,
wantErr: false,
},
{
name: "Get asset from GitHub release",
args: args{
templateURL: "https://github.com/some-owner/some-repo/releases/download/v1.0.0/cluster-template.yaml",
targetNamespace: "",
skipTemplateProcess: false,
},
want: template,
wantErr: false,
},
{
name: "Get asset from GitHub release + redirect",
args: args{
templateURL: "https://github.com/some-owner/some-repo/releases/download/v2.0.0/cluster-template.yaml",
targetNamespace: "",
skipTemplateProcess: false,
},
want: template,
wantErr: false,
},
{
name: "Get asset from GitHub release with a wrong URL",
args: args{
templateURL: "https://github.com/some-owner/some-repo/releases/wrong/v1.0.0/cluster-template.yaml",
targetNamespace: "",
skipTemplateProcess: false,
},
want: "",
wantErr: true,
},
{
name: "Get from stdin",
args: args{
Expand Down

0 comments on commit 39e99d3

Please sign in to comment.