Skip to content

Commit

Permalink
Add github client support
Browse files Browse the repository at this point in the history
Signed-off-by: Aniruddha Basak <[email protected]>
  • Loading branch information
aniruddha2000 committed Jan 29, 2024
1 parent 3bcc4fc commit 93a5567
Show file tree
Hide file tree
Showing 13 changed files with 401 additions and 89 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

.envrc
# Binaries for programs and plugins
*.exe
*.exe~
Expand Down
4 changes: 0 additions & 4 deletions github-release/docker-ferrol-1-27-v2/hashes.json

This file was deleted.

6 changes: 0 additions & 6 deletions github-release/docker-ferrol-1-27-v2/metadata.yaml

This file was deleted.

2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/SovereignCloudStack/csmctl
go 1.21

require (
github.com/SovereignCloudStack/cluster-stack-operator v0.1.0-alpha.2.0.20240119073057-7c5bfa7d3112
github.com/SovereignCloudStack/cluster-stack-operator v0.1.0-alpha.2.0.20240119145758-a04233f1485b
github.com/google/go-github/v56 v56.0.0
github.com/spf13/cobra v1.8.0
github.com/valyala/fasttemplate v1.2.2
Expand Down
6 changes: 2 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,8 @@ github.com/Microsoft/hcsshim v0.11.0 h1:7EFNIY4igHEXUdj1zXgAyU3fLc7QfOKHbkldRVTB
github.com/Microsoft/hcsshim v0.11.0/go.mod h1:OEthFdQv/AD2RAdzR6Mm1N1KPCztGKDurW1Z8b8VGMM=
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs=
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
github.com/SovereignCloudStack/cluster-stack-operator v0.1.0-alpha.2.0.20240119073057-7c5bfa7d3112 h1:w/1YnHrAI/Eihkzt6ewRQz9Bcr4yPU1dTOAf0JF0x6g=
github.com/SovereignCloudStack/cluster-stack-operator v0.1.0-alpha.2.0.20240119073057-7c5bfa7d3112/go.mod h1:1nOm+nYIGs8kansssJC8J/WOMbbbooR4bUJBVae7r90=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
github.com/SovereignCloudStack/cluster-stack-operator v0.1.0-alpha.2.0.20240119145758-a04233f1485b h1:U8sT5Fr5vdoZb/UHqNACqBLJyZ+tDDh3v8lxfXxGXR8=
github.com/SovereignCloudStack/cluster-stack-operator v0.1.0-alpha.2.0.20240119145758-a04233f1485b/go.mod h1:1nOm+nYIGs8kansssJC8J/WOMbbbooR4bUJBVae7r90=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
Expand Down
13 changes: 2 additions & 11 deletions pkg/clusterstack/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import (
// Component contains component.
type Component struct {
ClusterAddon string `yaml:"clusterAddon"`
NodeImage string `yaml:"nodeImage"`
NodeImage string `yaml:"nodeImage,omitempty"`
}

// Versions contains version information.
Expand All @@ -45,16 +45,7 @@ type MetaData struct {

// ParseMetaData parse the metadata file.
func ParseMetaData(path string) (MetaData, error) {
entries, err := os.ReadDir(path)
if err != nil {
return MetaData{}, fmt.Errorf("failed to read metadata directory: %w", err)
}

if len(entries) != 1 {
return MetaData{}, fmt.Errorf("ambiguous release found")
}

metadataPath := filepath.Join(path, entries[0].Name(), "metadata.yaml")
metadataPath := filepath.Join(path, "metadata.yaml")
fileInfo, err := os.ReadFile(filepath.Clean(metadataPath))
if err != nil {
return MetaData{}, fmt.Errorf("failed to read metadata file: %w", err)
Expand Down
41 changes: 27 additions & 14 deletions pkg/cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ limitations under the License.
package cmd

import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"

"github.com/SovereignCloudStack/csmctl/pkg/clusterstack"
"github.com/SovereignCloudStack/csmctl/pkg/github"
"github.com/SovereignCloudStack/csmctl/pkg/github/client"
"github.com/SovereignCloudStack/csmctl/pkg/hash"
"github.com/SovereignCloudStack/csmctl/pkg/template"
"github.com/spf13/cobra"
Expand All @@ -44,14 +46,12 @@ var (
note - Hash mode takes the last hash of the git commit.`
example = `csmctl create tests/cluster-stacks/docker/ferrol -m hash (for hash mode)
csmctl create tests/cluster-stacks/docker/ferrol -m hash --github-release github-release/ (for stable mode)`
csmctl create tests/cluster-stacks/docker/ferrol (for stable mode)`
)

var (
mode string
outputDirectory string
// TODO: remove this later.
githubReleasePath string
)

// CreateOptions contains config for creating a release.
Expand All @@ -77,12 +77,10 @@ var createCmd = &cobra.Command{
func init() {
createCmd.Flags().StringVarP(&mode, "mode", "m", "stable", "It defines the mode of the cluster stack manager")
createCmd.Flags().StringVarP(&outputDirectory, "output", "o", "./releases", "It defines the output directory in which the release artifacts will be generated")
// TODO: remove this later
createCmd.Flags().StringVar(&githubReleasePath, "github-release", "github-release", "It is used to get the path to local github release (for stable mode only)")
}

// GetCreateOptions create a Create Option for create command.
func GetCreateOptions(clusterStackPath string) (*CreateOptions, error) {
func GetCreateOptions(ctx context.Context, clusterStackPath string) (*CreateOptions, error) {
createOption := &CreateOptions{}

// ClusterAddon config
Expand All @@ -106,17 +104,32 @@ func GetCreateOptions(clusterStackPath string) (*CreateOptions, error) {
return nil, fmt.Errorf("failed to handle hash mode: %w", err)
}
case stableMode:
createOption.Metadata, err = clusterstack.HandleStableMode(githubReleasePath, createOption.CurrentReleaseHash, createOption.LatestReleaseHash)
gc, err := client.NewFactory().NewClient(ctx)
if err != nil {
return nil, fmt.Errorf("failed to handle stable mode: %w", err)
return nil, fmt.Errorf("failed to create new github client: %w", err)
}

// TODO: remove
latestReleaseInfoWithHash, err := github.GetLocalReleaseInfoWithHash(githubReleasePath)
latestRepoRelease, err := github.GetLatestReleaseFromRemoteRepository(ctx, mode, &config, gc)
if err != nil {
return nil, fmt.Errorf("failed to get latest github local release: %w", err)
return nil, fmt.Errorf("failed to get latest release form remote repository: %w", err)
}
fmt.Printf("latest release found: %q\n", latestRepoRelease)

if latestRepoRelease == "" {
createOption.Metadata.APIVersion = "metadata.clusterstack.x-k8s.io/v1alpha1"
createOption.Metadata.Versions.Kubernetes = config.Config.KubernetesVersion
createOption.Metadata.Versions.ClusterStack = "v1"
createOption.Metadata.Versions.Components.ClusterAddon = "v1"
} else {
if err := github.DownloadReleaseAssets(ctx, latestRepoRelease, "./tmp/releases/", gc); err != nil {
return nil, fmt.Errorf("failed to download release asset: %w", err)
}

createOption.Metadata, err = clusterstack.HandleStableMode("./tmp/releases/", createOption.CurrentReleaseHash, createOption.LatestReleaseHash)
if err != nil {
return nil, fmt.Errorf("failed to handle stable mode: %w", err)
}
}
createOption.LatestReleaseHash = latestReleaseInfoWithHash.Hash
}

releaseDirName, err := clusterstack.GetClusterStackReleaseDirectoryName(&createOption.Metadata, &createOption.Config)
Expand All @@ -129,7 +142,7 @@ func GetCreateOptions(clusterStackPath string) (*CreateOptions, error) {
return createOption, nil
}

func createAction(_ *cobra.Command, args []string) error {
func createAction(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("please provide a valid command, create only accept one argument to path to the cluster stacks")
}
Expand All @@ -140,7 +153,7 @@ func createAction(_ *cobra.Command, args []string) error {
return fmt.Errorf("mode is not supported please choose from - stable, hash")
}

createOpts, err := GetCreateOptions(clusterStackPath)
createOpts, err := GetCreateOptions(cmd.Context(), clusterStackPath)
if err != nil {
return fmt.Errorf("failed to create create options: %w", err)
}
Expand Down
62 changes: 62 additions & 0 deletions pkg/github/github.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package github

import (
"context"
"fmt"
"sort"

csoclusterstack "github.com/SovereignCloudStack/cluster-stack-operator/pkg/clusterstack"
"github.com/SovereignCloudStack/cluster-stack-operator/pkg/version"
"github.com/SovereignCloudStack/csmctl/pkg/clusterstack"
githubclient "github.com/SovereignCloudStack/csmctl/pkg/github/client"
)

// GetLatestReleaseFromRemoteRepository returns the latest release from the github repository.
func GetLatestReleaseFromRemoteRepository(ctx context.Context, mode string, config *clusterstack.CsmctlConfig, gc githubclient.Client) (string, error) {
ghReleases, resp, err := gc.ListRelease(ctx)
if err != nil {
return "", fmt.Errorf("failed to list releases on remote Git repository: %w", err)
}
if resp != nil && resp.StatusCode != 200 {
return "", fmt.Errorf("got unexpected status from call to remote Git repository: %s", resp.Status)
}

var clusterStacks csoclusterstack.ClusterStacks

for _, ghRelease := range ghReleases {
clusterStackObject, matches, err := matchesSpec(ghRelease.GetTagName(), mode, config)
if err != nil {
return "", fmt.Errorf("failed to get match release tag %q with spec of ClusterStack: %w", ghRelease.GetTagName(), err)
}

if matches {
clusterStacks = append(clusterStacks, clusterStackObject)
}
}

if len(clusterStacks) == 0 {
return "", nil
}

sort.Sort(clusterStacks)

str := clusterStacks.Latest().String()
return str, nil
}

func matchesSpec(releaseTagName, mode string, cs *clusterstack.CsmctlConfig) (csoclusterstack.ClusterStack, bool, error) {
csObject, err := csoclusterstack.NewFromClusterStackReleaseProperties(releaseTagName)
if err != nil {
return csoclusterstack.ClusterStack{}, false, fmt.Errorf("failed to get clusterstack object from string %q: %w", releaseTagName, err)
}

kubernetesVersion, err := cs.ParseKubernetesVersion()
if err != nil {
return csoclusterstack.ClusterStack{}, false, fmt.Errorf("failed to parse kubernetes version %q: %w", cs.Config.ClusterStackName, err)
}

return csObject, csObject.Version.Channel == version.Channel(mode) &&
csObject.KubernetesVersion.StringWithDot() == kubernetesVersion.StringWithDot() &&
csObject.Name == cs.Config.ClusterStackName &&
csObject.Provider == cs.Config.Provider.Type, nil
}
64 changes: 17 additions & 47 deletions pkg/github/release.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,63 +18,33 @@ limitations under the License.
package github

import (
"encoding/json"
"context"
"fmt"
"net/http"
"os"
"path/filepath"
"strings"

"github.com/SovereignCloudStack/csmctl/pkg/hash"
githubclient "github.com/SovereignCloudStack/csmctl/pkg/github/client"
)

// GHRelease contains fields for a release.
type GHRelease struct {
Provider string
ClusterStackName string
KubernetesVersionMajor string
KubernetesVersionMinor string
ClusterStackVersion string

Hash hash.ReleaseHash
}

// GetLocalReleaseInfoWithHash gets the local release info.
// TODO: Later replaced by the original github release fetching code.
func GetLocalReleaseInfoWithHash(path string) (GHRelease, error) {
entries, err := os.ReadDir(path)
// DownloadReleaseAssets downloads the specified release in the specified download path.
func DownloadReleaseAssets(ctx context.Context, releaseTag, downloadPath string, gc githubclient.Client) error {
repoRelease, resp, err := gc.GetReleaseByTag(ctx, releaseTag)
if err != nil {
return GHRelease{}, fmt.Errorf("failed to read github release path: %w", err)
return fmt.Errorf("failed to fetch release tag %q: %w", releaseTag, err)
}

if len(entries) != 1 {
return GHRelease{}, fmt.Errorf("ambiguous release found")
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to fetch release tag %s with status code %d", releaseTag, resp.StatusCode)
}

splittedReleaseName := strings.Split(entries[0].Name(), "-")

if len(splittedReleaseName) != 5 {
return GHRelease{}, fmt.Errorf("wrong release found")
}

ghRelease := GHRelease{
Provider: splittedReleaseName[0],
ClusterStackName: splittedReleaseName[1],
KubernetesVersionMajor: splittedReleaseName[2],
KubernetesVersionMinor: splittedReleaseName[3],
ClusterStackVersion: splittedReleaseName[4],
}

hashPath := filepath.Join(path, entries[0].Name(), "hashes.json")
hashFile, err := os.ReadFile(filepath.Clean(hashPath))
if err != nil {
return GHRelease{}, fmt.Errorf("failed to read hash.json: %w", err)
}
assetlist := []string{"metadata.yaml", "cluster-addon-values.yaml", "cluster-addon", "cluster-class"}

var data hash.ReleaseHash
if err := json.Unmarshal(hashFile, &data); err != nil {
return GHRelease{}, fmt.Errorf("failed to unmarshal release hash: %w", err)
if err := gc.DownloadReleaseAssets(ctx, repoRelease, downloadPath, assetlist); err != nil {
// if download failed for some reason, delete the release directory so that it can be retried in the next reconciliation
if err := os.RemoveAll(downloadPath); err != nil {
return fmt.Errorf("failed to remove release: %w", err)
}
return fmt.Errorf("failed to download release assets: %w", err)
}
ghRelease.Hash = data

return ghRelease, nil
return nil
}
Loading

0 comments on commit 93a5567

Please sign in to comment.