Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: fixes version check for Artifact Hub generation #339

Merged
merged 2 commits into from
May 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 48 additions & 8 deletions scripts/artifacthub/hub.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io"
"io/fs"
"net/http"
"os"
"path"
"path/filepath"
Expand Down Expand Up @@ -72,6 +73,19 @@ type ArtifactHubMetadata struct {
} `yaml:"annotations,omitempty"`
}

// HTTPClient is an interface that matches the methods needed from http.Client.
type HTTPClient interface {
Get(url string) (*http.Response, error)
}

// DefaultClient is a default implementation of the HTTPClient interface.
type DefaultClient struct{}

// Get is a method of DefaultClient that makes the actual HTTP GET request.
func (c DefaultClient) Get(url string) (*http.Response, error) {
return http.Get(url)
}

const (
// entryPoint is the directory entry point for artifact hub
ahEntryPoint = "artifacthub"
Expand Down Expand Up @@ -123,9 +137,11 @@ func main() {
panic(err)
}

githubSourceRelativePath := filepath.Join(entryPoint, entry.Name(), dir.Name(), "template.yaml")
createVersionDirectory(
rootDir,
filepath.Join(entryPoint, entry.Name(), dir.Name()),
githubSourceRelativePath,
constraintTemplate,
)
}
Expand All @@ -134,7 +150,7 @@ func main() {
}
}

func createVersionDirectory(rootDir, basePath string, constraintTemplate map[string]interface{}) {
func createVersionDirectory(rootDir, basePath, githubSourceRelativePath string, constraintTemplate map[string]interface{}) {
version := fmt.Sprintf("%s", constraintTemplate["metadata"].(map[string]interface{})["annotations"].(map[string]interface{})["metadata.gatekeeper.sh/version"])

// create directory if not exists
Expand All @@ -152,7 +168,7 @@ func createVersionDirectory(rootDir, basePath string, constraintTemplate map[str

// create artifacthub-pkg.yml file first then copy rest of the files. This will avoid unnecessary diff if there is any error while generating or updating artifacthub-pkg.yml
// add artifact hub metadata
addArtifactHubMetadata(filepath.Base(source), destination, ahBasePath, constraintTemplate)
addArtifactHubMetadata(filepath.Base(source), destination, ahBasePath, githubSourceRelativePath, constraintTemplate)

// copy directory content
err := copyDirectory(source, destination)
Expand All @@ -162,7 +178,7 @@ func createVersionDirectory(rootDir, basePath string, constraintTemplate map[str
}
}

func addArtifactHubMetadata(sourceDirectory, destinationPath, ahBasePath string, constraintTemplate map[string]interface{}) {
func addArtifactHubMetadata(sourceDirectory, destinationPath, ahBasePath, githubSourceRelativePath string, constraintTemplate map[string]interface{}) {
metadataFilePath := filepath.Join(destinationPath, "artifacthub-pkg.yml")

constraintTemplateHash := getConstraintTemplateHash(constraintTemplate)
Expand Down Expand Up @@ -199,7 +215,7 @@ func addArtifactHubMetadata(sourceDirectory, destinationPath, ahBasePath string,
}
} else {
// when metadata file already exists, check version to make sure it's updated if constraint template is changed
err := checkVersion(artifactHubMetadata, constraintTemplate, constraintTemplateHash)
err := checkVersion(&DefaultClient{}, artifactHubMetadata, githubSourceRelativePath)
if err != nil {
panic(err)
}
Expand All @@ -221,11 +237,35 @@ func addArtifactHubMetadata(sourceDirectory, destinationPath, ahBasePath string,
}
}

func checkVersion(artifactHubMetadata *ArtifactHubMetadata, constraintTemplate map[string]interface{}, newConstraintTemplateHash string) error {
// compare hash
if artifactHubMetadata.Digest != newConstraintTemplateHash {
func checkVersion(httpClient HTTPClient, artifactHubMetadata *ArtifactHubMetadata, githubSourceRelativePath string) error {
// compare hash with template.yaml in github
githubTemplateURL := sourceURL + githubSourceRelativePath
resp, err := httpClient.Get(githubTemplateURL)
if err != nil {
return fmt.Errorf("error while getting constraint template from github: %v", err)
}
if resp.StatusCode == http.StatusNotFound {
fmt.Printf("constraint template %s not found in github. It is likely that constraint template is being updated locally and not merged to github yet.\n", githubSourceRelativePath)

return nil
nilekhc marked this conversation as resolved.
Show resolved Hide resolved
}
defer resp.Body.Close()

githubConstraintTemplateBytes, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("error while reading constraint template from github")
}

githubConstraintTemplate := make(map[string]interface{})
err = yaml.Unmarshal(githubConstraintTemplateBytes, &githubConstraintTemplate)
if err != nil {
return fmt.Errorf("error while unmarshaling constraint template from github")
}

githubConstraintTemplateHash := getConstraintTemplateHash(githubConstraintTemplate)
if artifactHubMetadata.Digest != githubConstraintTemplateHash {
// compare version
if artifactHubMetadata.Version == constraintTemplate["metadata"].(map[string]interface{})["annotations"].(map[string]interface{})["metadata.gatekeeper.sh/version"].(string) {
if artifactHubMetadata.Version == githubConstraintTemplate["metadata"].(map[string]interface{})["annotations"].(map[string]interface{})["metadata.gatekeeper.sh/version"].(string) {
// panic if version is same but hash is different
return fmt.Errorf("looks like template.yaml is updated but the version is not. Please update the 'metadata.gatekeeper.sh/version' annotation in the template.yaml source")
}
Expand Down
94 changes: 74 additions & 20 deletions scripts/artifacthub/hub_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
package main

import (
"errors"
"io"
"net/http"
"os"
"strings"
"testing"

"gopkg.in/yaml.v3"
)

const (
Expand Down Expand Up @@ -159,61 +165,109 @@ func TestCopyDirectory(t *testing.T) {
}
}

// MockClient is a mock implementation of the HTTPClient interface for testing.
type MockClient struct {
Resp *http.Response
Err error
}

// Get is a method of MockClient that returns the pre-configured response and error.
func (c MockClient) Get(url string) (*http.Response, error) {
return c.Resp, c.Err
}

func TestCheckVersion(t *testing.T) {
testCases := []struct {
name string
artifactHubMetadata *ArtifactHubMetadata
constraintTemplate map[string]interface{}
newConstraintTemplateHash string
expectedError bool
name string
artifactHubMetadata *ArtifactHubMetadata
httpStatus int
httpError error
githubConstraintTemplate map[string]interface{}
expectedErrorMessage string
}{
{
name: "invalid version",
artifactHubMetadata: &ArtifactHubMetadata{
Digest: "invalid-digest",
Version: "v1.0.0",
},
newConstraintTemplateHash: "some-random-hash",
constraintTemplate: map[string]interface{}{
httpStatus: http.StatusOK,
githubConstraintTemplate: map[string]interface{}{
"metadata": map[string]interface{}{
"annotations": map[string]interface{}{
"metadata.gatekeeper.sh/version": "v1.0.0",
},
},
},
expectedError: true,
expectedErrorMessage: "looks like template.yaml is updated but the version is not. Please update the 'metadata.gatekeeper.sh/version' annotation in the template.yaml source",
},
{
name: "valid version with same digest",
artifactHubMetadata: &ArtifactHubMetadata{
Digest: "same-digest",
Digest: "600ca4d7048b1b64a5d80aaabf015eceef5d613c2f5a0a3d31e5360535d3c6e8",
},
httpStatus: http.StatusOK,
githubConstraintTemplate: map[string]interface{}{
"metadata": map[string]interface{}{
"annotations": map[string]interface{}{
"metadata.gatekeeper.sh/version": "v1.0.0",
},
},
},
newConstraintTemplateHash: "same-digest",
expectedError: false,
},
{
name: "valid version with different digest",
name: "valid version bump with different digest",
artifactHubMetadata: &ArtifactHubMetadata{
Digest: "digest",
Version: "v1.0.0",
Version: "v1.0.1",
},
newConstraintTemplateHash: "different-digest",
constraintTemplate: map[string]interface{}{
httpStatus: http.StatusOK,
githubConstraintTemplate: map[string]interface{}{
"metadata": map[string]interface{}{
"annotations": map[string]interface{}{
"metadata.gatekeeper.sh/version": "v1.0.1",
"metadata.gatekeeper.sh/version": "v1.0.0",
},
},
},
expectedError: false,
},
{
name: "template not found",
httpStatus: http.StatusNotFound,
},
{
name: "get template error",
httpStatus: http.StatusOK,
httpError: errors.New("fake error"),
expectedErrorMessage: "error while getting constraint template from github: fake error",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := checkVersion(tc.artifactHubMetadata, tc.constraintTemplate, tc.newConstraintTemplateHash)
if err != nil && !tc.expectedError {
t.Errorf("expected error to be nil")
githubConstraintTemplateBytes, err := yaml.Marshal(tc.githubConstraintTemplate)

// Create a mock client with a pre-configured response and error.
mockResp := &http.Response{
StatusCode: tc.httpStatus,
Body: io.NopCloser(strings.NewReader(string(githubConstraintTemplateBytes))),
}
mockClient := MockClient{
Resp: mockResp,
Err: tc.httpError,
}

err = checkVersion(mockClient, tc.artifactHubMetadata, "path/to/constraint/template.yaml")

if tc.expectedErrorMessage != "" {
if err == nil {
t.Errorf("Expected error '%s', but got no error", tc.expectedErrorMessage)
} else if err.Error() != tc.expectedErrorMessage {
t.Errorf("Expected error '%s', but got '%s'", tc.expectedErrorMessage, err.Error())
}
} else {
if err != nil {
t.Errorf("Expected no error, but got '%s'", err.Error())
}
}
})
}
Expand Down