Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
rbren committed May 19, 2020
0 parents commit 3017951
Show file tree
Hide file tree
Showing 11 changed files with 1,626 additions and 0 deletions.
70 changes: 70 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
module github.com/fairwindsops/nova

go 1.13

require (
github.com/DATA-DOG/go-sqlmock v1.4.1 // indirect
github.com/ashwanthkumar/slack-go-webhook v0.0.0-20200209025033-430dd4e66960
github.com/bradleyfalzon/ghinstallation v1.1.1
github.com/google/go-github/v28 v28.1.1
github.com/jmoiron/sqlx v1.2.0 // indirect
github.com/lib/pq v1.5.2 // indirect
github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2
github.com/parnurzeal/gorequest v0.2.16 // indirect
github.com/rubenv/sql-migrate v0.0.0-20200429072036-ae26b214fa43 // indirect
github.com/spf13/cobra v1.0.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.5.1
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
gopkg.in/yaml.v2 v2.2.8
helm.sh/helm v2.16.6+incompatible
helm.sh/helm/v3 v3.1.2
k8s.io/api v0.18.1 // indirect
k8s.io/apiextensions-apiserver v0.18.1 // indirect
k8s.io/apimachinery v0.18.1 // indirect
k8s.io/client-go v1.5.1
k8s.io/helm v2.16.5+incompatible
k8s.io/klog v1.0.0
moul.io/http2curl v1.0.0 // indirect
sigs.k8s.io/controller-runtime v0.5.2
)

replace (
// github.com/Azure/go-autorest/autorest has different versions for the Go
// modules than it does for releases on the repository. Note the correct
// version when updating.
github.com/Azure/go-autorest/autorest => github.com/Azure/go-autorest/autorest v0.9.0
github.com/docker/docker => github.com/moby/moby v0.7.3-0.20190826074503-38ab9da00309

// Kubernetes imports github.com/miekg/dns at a newer version but it is used
// by a package Helm does not need. Go modules resolves all packages rather
// than just those in use (like Glide and dep do). This sets the version
// to the one oras needs. If oras is updated the version should be updated
// as well.
github.com/miekg/dns => github.com/miekg/dns v0.0.0-20181005163659-0d29b283ac0f
gopkg.in/inf.v0 v0.9.1 => github.com/go-inf/inf v0.9.1
gopkg.in/square/go-jose.v2 v2.3.0 => github.com/square/go-jose v2.3.0+incompatible
k8s.io/api => k8s.io/api v0.0.0-20191016110408-35e52d86657a
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.0.0-20191114105449-027877536833
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20191028221656-72ed19daf4bb
k8s.io/apiserver => k8s.io/apiserver v0.0.0-20191114103151-9ca1dc586682
k8s.io/cli-runtime => k8s.io/cli-runtime v0.0.0-20191114110141-0a35778df828
k8s.io/client-go => k8s.io/client-go v0.0.0-20191016111102-bec269661e48
k8s.io/cloud-provider => k8s.io/cloud-provider v0.0.0-20191114112024-4bbba8331835
k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.0.0-20191114111741-81bb9acf592d
k8s.io/code-generator => k8s.io/code-generator v0.0.0-20191004115455-8e001e5d1894
k8s.io/component-base => k8s.io/component-base v0.0.0-20191114102325-35a9586014f7
k8s.io/cri-api => k8s.io/cri-api v0.0.0-20190828162817-608eb1dad4ac
k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.0.0-20191114112310-0da609c4ca2d
k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.0.0-20191114103820-f023614fb9ea
k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.0.0-20191114111510-6d1ed697a64b
k8s.io/kube-proxy => k8s.io/kube-proxy v0.0.0-20191114110717-50a77e50d7d9
k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.0.0-20191114111229-2e90afcb56c7
k8s.io/kubectl => k8s.io/kubectl v0.0.0-20191016120415-2ed914427d51
k8s.io/kubelet => k8s.io/kubelet v0.0.0-20191114110954-d67a8e7e2200
k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.0.0-20191114112655-db9be3e678bb
k8s.io/metrics => k8s.io/metrics v0.0.0-20191114105837-a4a2842dc51b
k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.0.0-20191114104439-68caf20693ac

rsc.io/letsencrypt => github.com/dmcgowan/letsencrypt v0.0.0-20160928181947-1847a81d2087
)
812 changes: 812 additions & 0 deletions go.sum

Large diffs are not rendered by default.

326 changes: 326 additions & 0 deletions pkg/helm/chartrepo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,326 @@
package helm

import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"sync"
"time"

version "github.com/mcuadros/go-version"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/release"

"gopkg.in/yaml.v2"
chartV2 "k8s.io/helm/pkg/proto/hapi/chart"
rspb "k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/klog"
)

// Repo represents a Helm chart Repo
type Repo struct {
URL string
Charts *ChartReleases
}

// ChartReleases contains the chart releases of a helm repository
type ChartReleases struct {
APIVersion string `yaml:"apiVersion"`
Entries map[string][]ChartRelease `yaml:"entries"`
}

// ChartRelease is a single chart version in a helm repository
type ChartRelease struct {
APIVersion string `yaml:"apiVersion,omitempty"`
AppVersion string `yaml:"appVersion"`
Created time.Time `yaml:"created"`
Description string `yaml:"description"`
Digest string `yaml:"digest,omitempty"`
Maintainers []chart.Maintainer `yaml:"maintainers,omitempty"`
Name string `yaml:"name"`
Urls []string `yaml:"urls"`
Version string `yaml:"version"`
Home string `json:"home"`
Sources []string `json:"sources"`
Keywords []string `json:"keywords"`
Icon string `json:"icon"`
Deprecated bool `json:"deprecated"`
}

// NewRepo returns data about a helm chart repository, given its url
func NewRepo(urls []string) []*Repo {
var repos []*Repo

var mutex = &sync.Mutex{}
var wg sync.WaitGroup
wg.Add(len(urls))

for _, url := range urls {
go func(address string) {
defer wg.Done()
repo := &Repo{
URL: address,
Charts: &ChartReleases{},
}
err := repo.loadReleases()
if err != nil {
klog.Warningf("Could not load chart repo: %s", address)
} else {
mutex.Lock()
repos = append(repos, repo)
mutex.Unlock()
}
}(url)
}

wg.Wait()
return repos
}

func (r *Repo) loadReleases() error {
response, err := http.Get(fmt.Sprintf("%s/index.yaml", r.URL))
if err != nil {
klog.Warningf("Error loading chart repo %s: %v", r.URL, err)
return err
}

data, err := ioutil.ReadAll(response.Body)
if err != nil {
klog.Warningf("Error reading chart data from repo %s: %v", r.URL, err)
return err
}

err = yaml.Unmarshal(data, r.Charts)
if err != nil {
klog.Warningf("Error unmarshalling yaml for chart repo %s: %v", r.URL, err)
return err
}
return nil
}

// NewestVersion returns the newest chart release for the provided release name
func (r *Repo) NewestVersion(releaseName string) *ChartRelease {
for name, entries := range r.Charts.Entries {
if name == releaseName {
var newest ChartRelease
for _, release := range entries {
if IsValidRelease(release.Version) {
if newest.Version == "" {
newest = release
}

foundNewer := version.Compare(release.Version, newest.Version, ">")
if foundNewer {
newest = release
}
}
}
return &newest
}
}
return nil
}

// NewestChartVersion returns the newest chart release for the provided release name and version
func (r *Repo) NewestChartVersion(currentChart *chart.Metadata) *ChartRelease {
for name, entries := range r.Charts.Entries {
if name == currentChart.Name {
var newest ChartRelease
repoHasCurrentVersion := false
for _, release := range entries {
if IsValidRelease(release.Version) {
if release.Version == currentChart.Version {
repoHasCurrentVersion = checkChartsSimilarity(currentChart, &release)
}

foundNewer := version.Compare(release.Version, newest.Version, ">")
if foundNewer {
newest = release
}
}
}
if repoHasCurrentVersion {
return &newest
}

}
}
return nil
}

// TryToFindNewestReleaseByChart will return the newest chart release given a collection of repos
func TryToFindNewestReleaseByChart(chart *release.Release, repos []*Repo) *ChartRelease {
newestRelease := &ChartRelease{}
for _, repo := range repos {
newestInRepo := repo.NewestChartVersion(chart.Chart.Metadata)
if newestInRepo == nil {
continue
}
if newestRelease == nil {
newestRelease = newestInRepo
} else {
if version.Compare(newestInRepo.Version, newestRelease.Version, ">") {
newestRelease = newestInRepo
}
}
}
return newestRelease
}

// TryToFindNewestReleaseByChartVersion2 will return the newest chart release given a collection of repos from helm2
func TryToFindNewestReleaseByChartVersion2(chart *rspb.Release, repos []*Repo) *ChartRelease {
newestRelease := &ChartRelease{}
for _, repo := range repos {
metadata, err := convertMetadataVersion2to3(chart.Chart.Metadata)

if err != nil {
klog.Errorf("Error converting helm2 metadata to helm3: %v", err)
continue
}

newestInRepo := repo.NewestChartVersion(metadata)
if newestInRepo == nil {
continue
}
if newestRelease == nil {
newestRelease = newestInRepo
} else {
if version.Compare(newestInRepo.Version, newestRelease.Version, ">") {
newestRelease = newestInRepo
}
}
}
return newestRelease
}

func checkChartsSimilarity(currentChartMeta *chart.Metadata, chartFromRepo *ChartRelease) bool {

if currentChartMeta.Home != chartFromRepo.Home {
return false
}

if currentChartMeta.Description != chartFromRepo.Description {
return false
}

for _, source := range currentChartMeta.Sources {
if !containsString(chartFromRepo.Sources, source) {
return false
}
}

chartFromRepoMaintainers := map[string]bool{}
for _, m := range chartFromRepo.Maintainers {
chartFromRepoMaintainers[m.Email+";"+m.Name+";"+m.URL] = true
}
for _, m := range currentChartMeta.Maintainers {
if !chartFromRepoMaintainers[m.Email+";"+m.Name+";"+m.URL] {
return false
}
}
return true
}

func containsString(arr []string, val string) bool {
for _, item := range arr {
if item == val {
return true
}
}
return false
}

// GetNewestReleaseByName will return the newest chart release given a collection of repos
func GetNewestReleaseByName(name string, repos []*Repo) *ChartRelease {
newestRelease := &ChartRelease{}
for _, repo := range repos {
newestInRepo := repo.NewestVersion(name)
if newestRelease == nil {
newestRelease = newestInRepo
} else {
if version.Compare(newestInRepo.Version, newestRelease.Version, ">") {
newestRelease = newestInRepo
}
}
}
return newestRelease
}

// GetChartInfo returns info about a chart with the version specified
func GetChartInfo(name string, version string, repos []*Repo) *ChartRelease {
for _, repo := range repos {
for key, chart := range repo.Charts.Entries {
if key == name {
for _, release := range chart {
if release.Version == version {
return &release
}
}
}
}
}
return nil
}

// IsValidRelease returns a bool indicating whether a version string is valid or not.
func IsValidRelease(version string) bool {
var specialForms = []string{
"SNAPSHOT",
"snapshot",
"dev",
"alpha",
"a",
"beta",
"b",
"RC",
"rc",
"#",
"p",
"pl",
}

for _, invalid := range specialForms {
if strings.Contains(version, invalid) {
return false
}
}
return true
}

// IsRepoIncluded check if the repo is included in the list of repos
func IsRepoIncluded(chartName string, repos []*Repo) []*Repo {
found := []*Repo{}
for _, repo := range repos {
if contains(chartName, repo) {
found = append(found, repo)
}
}
return found
}

func contains(chartName string, repo *Repo) bool {
for name := range repo.Charts.Entries {
if name == chartName {
return true
}
}
return false
}

func convertMetadataVersion2to3(metatataV2 *chartV2.Metadata) (*chart.Metadata, error) {

metadata := new(chart.Metadata)
jsonData, err := json.Marshal(metatataV2)
if err != nil {
return nil, err
}

err = json.Unmarshal(jsonData, metadata)
if err != nil {
return nil, err
}

return metadata, err
}
Loading

0 comments on commit 3017951

Please sign in to comment.