Skip to content
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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ require (
k8s.io/client-go v0.18.0
k8s.io/klog v1.0.0
rsc.io/letsencrypt v0.0.3 // indirect
sigs.k8s.io/yaml v1.2.0
)

replace (
Expand Down
59 changes: 0 additions & 59 deletions go.sum

Large diffs are not rendered by default.

55 changes: 55 additions & 0 deletions pkg/helm/actions/fake/helmcrconfigs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package fake

import (
"fmt"
"net/http"
"net/http/httptest"
"strconv"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/dynamic/fake"
)

func newUnstructured(apiVersion, kind, namespace, name string) *unstructured.Unstructured {
return &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": apiVersion,
"kind": kind,
"metadata": map[string]interface{}{
"namespace": namespace,
"name": name,
},
},
}
}

func fakeHelmCR(fakeIndexFile string, name string) *unstructured.Unstructured {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/yaml")
fmt.Fprintln(w, fakeIndexFile)
}))
sampleRepoCR := newUnstructured("helm.openshift.io/v1beta1", "HelmChartRepository", "", name)
connectionConfig := map[string]interface{}{
"url": ts.URL,
}
sampleRepoCR.Object["spec"] = map[string]interface{}{
"connectionConfig": connectionConfig,
"name": name,
}
return sampleRepoCR
}

func SetupClusterWithHelmCrs(indexFiles []string) dynamic.Interface {
scheme := runtime.NewScheme()
var objs []runtime.Object

for i, indexFile := range indexFiles {
fakeCr := fakeHelmCR(indexFile, "sample-repo-"+strconv.Itoa(i+1))
objs = append(objs, fakeCr)
}

client := fake.NewSimpleDynamicClient(scheme, objs...)
return client
}
14 changes: 14 additions & 0 deletions pkg/helm/actions/get_dynamic_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package actions

import (
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest"
)

func DynamicClient(conf *rest.Config) (dynamic.Interface, error) {
client, err := dynamic.NewForConfig(conf)
if err != nil {
return nil, err
}
return client, nil
}
274 changes: 274 additions & 0 deletions pkg/helm/actions/get_repos.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
package actions

import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io/ioutil"
"net/http"
"strings"

"helm.sh/helm/v3/pkg/repo"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
"sigs.k8s.io/yaml"

"github.com/openshift/console/pkg/crypto"
)

var (
helmChartRepositoryGVK = schema.GroupVersionResource{
Group: "helm.openshift.io",
Version: "v1beta1",
Resource: "helmchartrepositories",
}

defaultRepo = unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "helm.openshift.io/v1beta1",
"kind": "HelmChartRepository",
"metadata": map[string]interface{}{
"namespace": "",
"name": "redhat-helm-chart",
},
"spec": map[string]interface{}{
"connectionConfig": map[string]interface{}{
"url": "https://redhat-developer.github.io/redhat-helm-charts",
},
},
},
}
)

const (
configNamespace = "openshift-config"
)

type TLSConfigGetter interface {
Get() (*tls.Config, error)
}

type HelmConfigGetter struct {
Client dynamic.Interface
CoreClient corev1.CoreV1Interface

DefaultRepoCACertificate []byte
}

func (b HelmConfigGetter) IndexFiles(helmConfigs []*HelmConfig) []*repo.IndexFile {
var indexFiles []*repo.IndexFile
for _, helmConfig := range helmConfigs {
indexFile, err := b.IndexFile(helmConfig)
if err != nil {
plog.Errorf("Failed to load helm IndexFile %s", err.Error())
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a heads up that this will conflict with #1862, which switches logging to klog. We might hold #1862 until after this merges.

cc @zherman0 @jhadvig

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure

}
indexFiles = append(indexFiles, indexFile)
}
return indexFiles
}

func (b HelmConfigGetter) parseHelmConfig(repo unstructured.Unstructured) (*HelmConfig, error) {
h := &HelmConfig{}
url, _, err := unstructured.NestedString(repo.Object, "spec", "connectionConfig", "url")
if err != nil {
return nil, err
}

name, _, err := unstructured.NestedString(repo.Object, "metadata", "name")
if err != nil {
return nil, err
}

caReference, _, err := unstructured.NestedString(repo.Object, "spec", "connectionConfig", "ca", "name")
if err != nil {
return nil, err
}

tlsReference, _, err := unstructured.NestedString(repo.Object, "spec", "connectionConfig", "tlsconfig", "name")
if err != nil {
return nil, err
}

h.Name = name
if caReference != "" {
h.CACert, err = b.ConfigMapValue(caReference, "ca-bundle.crt")
if err != nil {
return nil, err
}
}
if tlsReference != "" {
h.TLSCert, err = b.SecretValue(tlsReference, "tls.crt")
if err != nil {
return nil, err
}
h.TLSKey, err = b.SecretValue(tlsReference, "tls.key")
if err != nil {
return nil, err
}
}
h.Url = url
return h, nil
}

func (b HelmConfigGetter) MergeIndexFiles(files ...*repo.IndexFile) *repo.IndexFile {
indexFile := repo.NewIndexFile()
for _, file := range files {
for key, entry := range file.Entries {
indexFile.Entries[key] = entry
}
}
return indexFile
}

func (b HelmConfigGetter) ConfigMapValue(name string, dataField string) ([]byte, error) {
configMap, err := b.CoreClient.ConfigMaps(configNamespace).Get(context.TODO(), name, v1.GetOptions{})
if err != nil {
return nil, errors.New(fmt.Sprintf("Failed to find %s key in configmap %s", dataField, name))
}
if val, ok := configMap.Data[dataField]; ok {
return []byte(val), nil
}
return nil, errors.New(fmt.Sprintf("Failed to find %s key in configmap %s", dataField, name))
}

func (b HelmConfigGetter) SecretValue(name string, dataField string) ([]byte, error) {
secret, err := b.CoreClient.Secrets(configNamespace).Get(context.TODO(), name, v1.GetOptions{})
if err != nil {
return nil, errors.New(fmt.Sprintf("Failed to find %s key in secret %s", dataField, name))

}
if val, ok := secret.Data[dataField]; ok {
return val, nil
}
return nil, errors.New(fmt.Sprintf("Failed to find %s key in secret %s", dataField, name))
}

type HelmConfig struct {
Name string
Url string
CACert []byte
TLSCert []byte
TLSKey []byte
}

func (b HelmConfigGetter) Get(caCert, tlsCert, tlsKey []byte) (*tls.Config, error) {
mTLSConfig := &tls.Config{
CipherSuites: crypto.DefaultCiphers(),
}
var rootCAs *x509.CertPool
var err error

if caCert != nil {
rootCAs = x509.NewCertPool()
if ok := rootCAs.AppendCertsFromPEM(caCert); !ok {
return nil, errors.New("Failed to append caCert")
}
} else {
rootCAs, err = x509.SystemCertPool()
if err != nil {
return nil, err
}
}
mTLSConfig.ClientCAs = rootCAs

if tlsKey != nil && tlsCert != nil {
cert, err := tls.X509KeyPair(tlsCert, tlsKey)
if err != nil {
return nil, err
}
mTLSConfig.Certificates = []tls.Certificate{cert}
}
return mTLSConfig, nil
}

func (b HelmConfigGetter) HttpClient(helmConfig *HelmConfig) (*http.Client, error) {
mTLSConfig, err := b.Get(helmConfig.CACert, helmConfig.TLSCert, helmConfig.TLSKey)
if err != nil {
return nil, err
}
tr := &http.Transport{
TLSClientConfig: mTLSConfig,
}

client := &http.Client{Transport: tr}
return client, nil
}

func (b HelmConfigGetter) IndexFile(helmConfig *HelmConfig) (*repo.IndexFile, error) {
plog.Println("HelmConfig", helmConfig)
var indexFile repo.IndexFile
httpClient, err := b.HttpClient(helmConfig)
if err != nil {
return nil, err
}
if !strings.HasSuffix(helmConfig.Url, "/index.yaml") {
helmConfig.Url += "/index.yaml"
}
resp, err := httpClient.Get(helmConfig.Url)
if err != nil {
return nil, err
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
err = yaml.Unmarshal(body, &indexFile)
if err != nil {
return nil, err
}
return &indexFile, nil
}

func (b *HelmConfigGetter) List() ([]*HelmConfig, error) {
var helmConfigs []*HelmConfig
repos, err := b.Client.Resource(helmChartRepositoryGVK).List(context.TODO(), v1.ListOptions{})
if err != nil || len(repos.Items) == 0 {
// In case no HelmRepoCRs configured, use default redhat helm chart repo
helmConfig, err := b.parseHelmConfig(defaultRepo)
if err != nil {
return helmConfigs, nil
}
helmConfig.CACert = b.DefaultRepoCACertificate
helmConfigs = append(helmConfigs, helmConfig)
return helmConfigs, nil
}
for _, item := range repos.Items {
helmConfig, err := b.parseHelmConfig(item)
if err != nil {
return nil, err
}
helmConfigs = append(helmConfigs, helmConfig)
}
return helmConfigs, nil
}

func newHelmConfigGetter(client dynamic.Interface, corev1Client corev1.CoreV1Interface, defaultRepoCACerts []byte) (*HelmConfigGetter, error) {
helmConfigGetter := &HelmConfigGetter{
Client: client,
CoreClient: corev1Client,
DefaultRepoCACertificate: defaultRepoCACerts,
}
return helmConfigGetter, nil
}

func FetchIndexFile(client dynamic.Interface, corev1Client corev1.CoreV1Interface, defaultRepoCACerts []byte) (*repo.IndexFile, error) {
helmConfigBuilder, err := newHelmConfigGetter(client, corev1Client, defaultRepoCACerts)
if err != nil {
return nil, err
}

helmConfigs, err := helmConfigBuilder.List()
if err != nil {
return nil, err
}

indexFiles := helmConfigBuilder.IndexFiles(helmConfigs)
indexFile := helmConfigBuilder.MergeIndexFiles(indexFiles...)

return indexFile, nil
}
Loading