Support Helm charts from Bucket sources

This commit is contained in:
Hidde Beydals 2020-09-18 18:07:30 +02:00
parent ce5fc3e5a7
commit 03ce9d96da
5 changed files with 85 additions and 97 deletions

View File

@ -55,8 +55,8 @@ type LocalHelmChartSourceReference struct {
// +optional // +optional
APIVersion string `json:"apiVersion,omitempty"` APIVersion string `json:"apiVersion,omitempty"`
// Kind of the referent, valid values are ('HelmRepository', 'GitRepository'). // Kind of the referent, valid values are ('HelmRepository', 'GitRepository', 'Bucket').
// +kubebuilder:validation:Enum=HelmRepository;GitRepository // +kubebuilder:validation:Enum=HelmRepository;GitRepository;Bucket
// +required // +required
Kind string `json:"kind"` Kind string `json:"kind"`

View File

@ -73,10 +73,11 @@ spec:
type: string type: string
kind: kind:
description: Kind of the referent, valid values are ('HelmRepository', description: Kind of the referent, valid values are ('HelmRepository',
'GitRepository'). 'GitRepository', 'Bucket').
enum: enum:
- HelmRepository - HelmRepository
- GitRepository - GitRepository
- Bucket
type: string type: string
name: name:
description: Name of the referent. description: Name of the referent.

View File

@ -119,33 +119,39 @@ func (r *HelmChartReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
log.Error(err, "unable to purge old artifacts") log.Error(err, "unable to purge old artifacts")
} }
// Retrieve the source
source, err := r.getSource(ctx, chart)
if err != nil {
chart = sourcev1.HelmChartNotReady(*chart.DeepCopy(), sourcev1.ChartPullFailedReason, err.Error())
if err := r.Status().Update(ctx, &chart); err != nil {
log.Error(err, "unable to update status")
}
return ctrl.Result{Requeue: true}, err
}
// Assert source is ready
if source.GetArtifact() == nil {
err = fmt.Errorf("no artifact found for source `%s` kind '%s'",
chart.Spec.SourceRef.Name, chart.Spec.SourceRef.Kind)
chart = sourcev1.HelmChartNotReady(*chart.DeepCopy(), sourcev1.ChartPullFailedReason, err.Error())
if err := r.Status().Update(ctx, &chart); err != nil {
log.Error(err, "unable to update status")
}
return ctrl.Result{Requeue: true}, err
}
// Perform the reconciliation for the chart source type // Perform the reconciliation for the chart source type
var reconciledChart sourcev1.HelmChart var reconciledChart sourcev1.HelmChart
var reconcileErr error var reconcileErr error
switch chart.Spec.SourceRef.Kind { switch typedSource := source.(type) {
case sourcev1.HelmRepositoryKind: case *sourcev1.HelmRepository:
repository, err := r.getChartRepositoryWithArtifact(ctx, chart) reconciledChart, reconcileErr = r.reconcileFromHelmRepository(ctx, *typedSource, *chart.DeepCopy(), changed)
if err != nil { case *sourcev1.GitRepository, *sourcev1.Bucket:
chart = sourcev1.HelmChartNotReady(*chart.DeepCopy(), sourcev1.ChartPullFailedReason, err.Error()) reconciledChart, reconcileErr = r.reconcileFromTarballArtifact(ctx, *typedSource.GetArtifact(),
if err := r.Status().Update(ctx, &chart); err != nil { *chart.DeepCopy(), changed)
log.Error(err, "unable to update status")
}
return ctrl.Result{Requeue: true}, err
}
reconciledChart, reconcileErr = r.reconcileFromHelmRepository(ctx, repository, *chart.DeepCopy(), changed)
case sourcev1.GitRepositoryKind:
repository, err := r.getGitRepositoryWithArtifact(ctx, chart)
if err != nil {
chart = sourcev1.HelmChartNotReady(*chart.DeepCopy(), sourcev1.ChartPullFailedReason, err.Error())
if err := r.Status().Update(ctx, &chart); err != nil {
log.Error(err, "unable to update status")
}
return ctrl.Result{Requeue: true}, err
}
reconciledChart, reconcileErr = r.reconcileFromGitRepository(ctx, repository, *chart.DeepCopy(), changed)
default: default:
err := fmt.Errorf("unable to reconcile unsupported source reference kind '%s'", chart.Spec.SourceRef.Kind) err := fmt.Errorf("unable to reconcile unsupported source reference kind '%s'", chart.Spec.SourceRef.Kind)
return ctrl.Result{}, err return ctrl.Result{Requeue: false}, err
} }
// Update status with the reconciliation result // Update status with the reconciliation result
@ -188,6 +194,41 @@ func (r *HelmChartReconciler) SetupWithManagerAndOptions(mgr ctrl.Manager, opts
Complete(r) Complete(r)
} }
func (r *HelmChartReconciler) getSource(ctx context.Context, chart sourcev1.HelmChart) (sourcev1.Source, error) {
var source sourcev1.Source
namespacedName := types.NamespacedName{
Namespace: chart.GetNamespace(),
Name: chart.Spec.SourceRef.Name,
}
switch chart.Spec.SourceRef.Kind {
case sourcev1.HelmRepositoryKind:
var repository sourcev1.HelmRepository
err := r.Client.Get(ctx, namespacedName, &repository)
if err != nil {
return source, fmt.Errorf("failed to retrieve source: %w", err)
}
source = &repository
case sourcev1.GitRepositoryKind:
var repository sourcev1.GitRepository
err := r.Client.Get(ctx, namespacedName, &repository)
if err != nil {
return source, fmt.Errorf("failed to retrieve source: %w", err)
}
source = &repository
case sourcev1.BucketKind:
var bucket sourcev1.Bucket
err := r.Client.Get(ctx, namespacedName, &bucket)
if err != nil {
return source, fmt.Errorf("failed to retrieve source: %w", err)
}
source = &bucket
default:
return source, fmt.Errorf("source `%s` kind '%s' not supported",
chart.Spec.SourceRef.Name, chart.Spec.SourceRef.Kind)
}
return source, nil
}
func (r *HelmChartReconciler) reconcileFromHelmRepository(ctx context.Context, func (r *HelmChartReconciler) reconcileFromHelmRepository(ctx context.Context,
repository sourcev1.HelmRepository, chart sourcev1.HelmChart, force bool) (sourcev1.HelmChart, error) { repository sourcev1.HelmRepository, chart sourcev1.HelmChart, force bool) (sourcev1.HelmChart, error) {
cv, err := helm.GetDownloadableChartVersionFromIndex(r.Storage.LocalPath(*repository.GetArtifact()), cv, err := helm.GetDownloadableChartVersionFromIndex(r.Storage.LocalPath(*repository.GetArtifact()),
@ -201,8 +242,8 @@ func (r *HelmChartReconciler) reconcileFromHelmRepository(ctx context.Context,
fmt.Sprintf("%s-%s.tgz", cv.Name, cv.Version)) fmt.Sprintf("%s-%s.tgz", cv.Name, cv.Version))
if !force && repository.GetArtifact() != nil && repository.GetArtifact().Revision == cv.Version { if !force && repository.GetArtifact() != nil && repository.GetArtifact().Revision == cv.Version {
if artifact.URL != repository.GetArtifact().URL { if artifact.URL != repository.GetArtifact().URL {
r.Storage.SetArtifactURL(repository.GetArtifact()) r.Storage.SetArtifactURL(chart.GetArtifact())
repository.Status.URL = r.Storage.SetHostname(repository.Status.URL) repository.Status.URL = r.Storage.SetHostname(chart.Status.URL)
} }
return chart, nil return chart, nil
} }
@ -353,35 +394,8 @@ func (r *HelmChartReconciler) reconcileFromHelmRepository(ctx context.Context,
return sourcev1.HelmChartReady(chart, artifact, chartUrl, readyReason, readyMessage), nil return sourcev1.HelmChartReady(chart, artifact, chartUrl, readyReason, readyMessage), nil
} }
// getChartRepositoryWithArtifact attempts to get the v1alpha1.HelmRepository func (r *HelmChartReconciler) reconcileFromTarballArtifact(ctx context.Context,
// for the given chart. It returns an error if the HelmRepository could artifact sourcev1.Artifact, chart sourcev1.HelmChart, force bool) (sourcev1.HelmChart, error) {
// not be retrieved or if does not have an artifact.
func (r *HelmChartReconciler) getChartRepositoryWithArtifact(ctx context.Context, chart sourcev1.HelmChart) (sourcev1.HelmRepository, error) {
if chart.Spec.SourceRef.Name == "" {
return sourcev1.HelmRepository{}, fmt.Errorf("no HelmRepository reference given")
}
name := types.NamespacedName{
Namespace: chart.GetNamespace(),
Name: chart.Spec.SourceRef.Name,
}
var repository sourcev1.HelmRepository
err := r.Client.Get(ctx, name, &repository)
if err != nil {
err = fmt.Errorf("failed to get HelmRepository '%s': %w", name, err)
return repository, err
}
if repository.GetArtifact() == nil {
err = fmt.Errorf("no repository index artifact found for HelmRepository '%s'", name)
}
return repository, err
}
func (r *HelmChartReconciler) reconcileFromGitRepository(ctx context.Context,
repository sourcev1.GitRepository, chart sourcev1.HelmChart, force bool) (sourcev1.HelmChart, error) {
// Create temporary working directory // Create temporary working directory
tmpDir, err := ioutil.TempDir("", fmt.Sprintf("%s-%s-", chart.Namespace, chart.Name)) tmpDir, err := ioutil.TempDir("", fmt.Sprintf("%s-%s-", chart.Namespace, chart.Name))
if err != nil { if err != nil {
@ -390,8 +404,8 @@ func (r *HelmChartReconciler) reconcileFromGitRepository(ctx context.Context,
} }
defer os.RemoveAll(tmpDir) defer os.RemoveAll(tmpDir)
// Open GitRepository artifact file and untar files into working directory // Open the tarball artifact file and untar files into working directory
f, err := os.Open(r.Storage.LocalPath(*repository.GetArtifact())) f, err := os.Open(r.Storage.LocalPath(artifact))
if err != nil { if err != nil {
err = fmt.Errorf("artifact open error: %w", err) err = fmt.Errorf("artifact open error: %w", err)
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
@ -418,12 +432,12 @@ func (r *HelmChartReconciler) reconcileFromGitRepository(ctx context.Context,
} }
// Return early if the revision is still the same as the current chart artifact // Return early if the revision is still the same as the current chart artifact
artifact := r.Storage.NewArtifactFor(chart.Kind, chart.ObjectMeta.GetObjectMeta(), chartMetadata.Version, chartArtifact := r.Storage.NewArtifactFor(chart.Kind, chart.ObjectMeta.GetObjectMeta(), chartMetadata.Version,
fmt.Sprintf("%s-%s.tgz", chartMetadata.Name, chartMetadata.Version)) fmt.Sprintf("%s-%s.tgz", chartMetadata.Name, chartMetadata.Version))
if !force && chart.GetArtifact() != nil && chart.GetArtifact().Revision == chartMetadata.Version { if !force && chart.GetArtifact() != nil && chart.GetArtifact().Revision == chartMetadata.Version {
if artifact.URL != repository.GetArtifact().URL { if chartArtifact.URL != artifact.URL {
r.Storage.SetArtifactURL(repository.GetArtifact()) r.Storage.SetArtifactURL(&chartArtifact)
repository.Status.URL = r.Storage.SetHostname(repository.Status.URL) chart.Status.URL = r.Storage.SetHostname(chart.Status.URL)
} }
return chart, nil return chart, nil
} }
@ -436,14 +450,14 @@ func (r *HelmChartReconciler) reconcileFromGitRepository(ctx context.Context,
} }
// Ensure artifact directory exists // Ensure artifact directory exists
err = r.Storage.MkdirAll(artifact) err = r.Storage.MkdirAll(chartArtifact)
if err != nil { if err != nil {
err = fmt.Errorf("unable to create artifact directory: %w", err) err = fmt.Errorf("unable to create artifact directory: %w", err)
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
} }
// Acquire a lock for the artifact // Acquire a lock for the artifact
unlock, err := r.Storage.Lock(artifact) unlock, err := r.Storage.Lock(chartArtifact)
if err != nil { if err != nil {
err = fmt.Errorf("unable to acquire lock: %w", err) err = fmt.Errorf("unable to acquire lock: %w", err)
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
@ -466,7 +480,7 @@ func (r *HelmChartReconciler) reconcileFromGitRepository(ctx context.Context,
err = fmt.Errorf("failed to open chart package: %w", err) err = fmt.Errorf("failed to open chart package: %w", err)
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
} }
if err := r.Storage.Copy(&artifact, cf); err != nil { if err := r.Storage.Copy(&chartArtifact, cf); err != nil {
cf.Close() cf.Close()
err = fmt.Errorf("failed to copy chart package to storage: %w", err) err = fmt.Errorf("failed to copy chart package to storage: %w", err)
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
@ -474,41 +488,14 @@ func (r *HelmChartReconciler) reconcileFromGitRepository(ctx context.Context,
cf.Close() cf.Close()
// Update symlink // Update symlink
cUrl, err := r.Storage.Symlink(artifact, fmt.Sprintf("%s-latest.tgz", chartMetadata.Name)) cUrl, err := r.Storage.Symlink(chartArtifact, fmt.Sprintf("%s-latest.tgz", chartMetadata.Name))
if err != nil { if err != nil {
err = fmt.Errorf("storage error: %w", err) err = fmt.Errorf("storage error: %w", err)
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
} }
message := fmt.Sprintf("Fetched and packaged revision: %s", artifact.Revision) message := fmt.Sprintf("Fetched and packaged revision: %s", chartArtifact.Revision)
return sourcev1.HelmChartReady(chart, artifact, cUrl, sourcev1.ChartPackageSucceededReason, message), nil return sourcev1.HelmChartReady(chart, chartArtifact, cUrl, sourcev1.ChartPackageSucceededReason, message), nil
}
// getGitRepositoryWithArtifact attempts to get the GitRepository for the given
// chart. It returns an error if the v1alpha1.GitRepository could not be retrieved
// or does not have an artifact.
func (r *HelmChartReconciler) getGitRepositoryWithArtifact(ctx context.Context, chart sourcev1.HelmChart) (sourcev1.GitRepository, error) {
if chart.Spec.SourceRef.Name == "" {
return sourcev1.GitRepository{}, fmt.Errorf("no GitRepository reference given")
}
name := types.NamespacedName{
Namespace: chart.GetNamespace(),
Name: chart.Spec.SourceRef.Name,
}
var repository sourcev1.GitRepository
err := r.Client.Get(ctx, name, &repository)
if err != nil {
err = fmt.Errorf("failed to get GitRepository '%s': %w", name, err)
return repository, err
}
if repository.GetArtifact() == nil {
err = fmt.Errorf("no artifact found for GitRepository '%s'", repository.Name)
}
return repository, err
} }
// resetStatus returns a modified v1alpha1.HelmChart and a boolean indicating // resetStatus returns a modified v1alpha1.HelmChart and a boolean indicating

View File

@ -152,7 +152,7 @@ var _ = Describe("HelmChartReconciler", func() {
_ = k8sClient.Get(context.Background(), key, updated) _ = k8sClient.Get(context.Background(), key, updated)
for _, c := range updated.Status.Conditions { for _, c := range updated.Status.Conditions {
if c.Reason == sourcev1.ChartPullFailedReason && if c.Reason == sourcev1.ChartPullFailedReason &&
strings.Contains(c.Message, "failed to get HelmRepository") { strings.Contains(c.Message, "failed to retrieve source") {
return true return true
} }
} }

View File

@ -1600,7 +1600,7 @@ string
</em> </em>
</td> </td>
<td> <td>
<p>Kind of the referent, valid values are (&lsquo;HelmRepository&rsquo;, &lsquo;GitRepository&rsquo;).</p> <p>Kind of the referent, valid values are (&lsquo;HelmRepository&rsquo;, &lsquo;GitRepository&rsquo;, &lsquo;Bucket&rsquo;).</p>
</td> </td>
</tr> </tr>
<tr> <tr>