kustomize-controller/controllers/kustomization_controller.go

167 lines
5.4 KiB
Go

/*
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controllers
import (
"context"
"fmt"
"io/ioutil"
"os"
"os/exec"
"time"
"github.com/go-logr/logr"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1alpha1"
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
)
// KustomizationReconciler reconciles a Kustomization object
type KustomizationReconciler struct {
client.Client
Log logr.Logger
Scheme *runtime.Scheme
}
// +kubebuilder:rbac:groups=kustomize.fluxcd.io,resources=kustomizations,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=kustomize.fluxcd.io,resources=kustomizations/status,verbs=get;update;patch
func (r *KustomizationReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
var kustomization kustomizev1.Kustomization
if err := r.Get(ctx, req.NamespacedName, &kustomization); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
log := r.Log.WithValues(kustomization.Kind, req.NamespacedName)
// get artifact source
var repository sourcev1.GitRepository
repositoryName := types.NamespacedName{
Namespace: kustomization.GetNamespace(),
Name: kustomization.Spec.GitRepositoryRef.Name,
}
err := r.Client.Get(ctx, repositoryName, &repository)
if err != nil {
log.Error(err, "GitRepository not found", "gitrepository", repositoryName)
return ctrl.Result{Requeue: true}, err
}
// try git sync
syncedKustomization, err := r.sync(ctx, *kustomization.DeepCopy(), repository)
if err != nil {
log.Error(err, "Kustomization sync failed")
}
// update status
if err := r.Status().Update(ctx, &syncedKustomization); err != nil {
log.Error(err, "unable to update Kustomization status")
return ctrl.Result{Requeue: true}, err
}
log.Info("Kustomization sync finished", "msg", kustomizev1.KustomizationReadyMessage(syncedKustomization))
// requeue kustomization
return ctrl.Result{RequeueAfter: kustomization.Spec.Interval.Duration}, nil
}
func (r *KustomizationReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&kustomizev1.Kustomization{}).
WithEventFilter(KustomizationSyncAtPredicate{}).
Complete(r)
}
func (r *KustomizationReconciler) sync(
ctx context.Context,
kustomization kustomizev1.Kustomization,
repository sourcev1.GitRepository) (kustomizev1.Kustomization, error) {
if repository.Status.Artifact == nil || repository.Status.Artifact.URL == "" {
err := fmt.Errorf("artifact not found in %s", repository.GetName())
return kustomizev1.KustomizationNotReady(kustomization, kustomizev1.ArtifactFailedReason, err.Error()), err
}
// create tmp dir
tmpDir, err := ioutil.TempDir("", repository.Name)
if err != nil {
err = fmt.Errorf("tmp dir error: %w", err)
return kustomizev1.KustomizationNotReady(kustomization, sourcev1.StorageOperationFailedReason, err.Error()), err
}
defer os.RemoveAll(tmpDir)
// download artifact and extract files
url := repository.Status.Artifact.URL
cmd := fmt.Sprintf("cd %s && curl -sL %s | tar -xz --strip-components=1 -C .", tmpDir, url)
command := exec.CommandContext(ctx, "/bin/sh", "-c", cmd)
output, err := command.CombinedOutput()
if err != nil {
err = fmt.Errorf("artifact acquisition failed: %w", err)
return kustomizev1.KustomizationNotReady(
kustomization,
kustomizev1.ArtifactFailedReason,
err.Error(),
), fmt.Errorf("artifact download `%s` error: %s", url, string(output))
}
// kustomize build
buildDir := kustomization.Spec.Path
cmd = fmt.Sprintf("cd %s && kustomize build %s > %s.yaml", tmpDir, buildDir, kustomization.GetName())
command = exec.CommandContext(ctx, "/bin/sh", "-c", cmd)
output, err = command.CombinedOutput()
if err != nil {
err = fmt.Errorf("kustomize build error: %w", err)
fmt.Println(string(output))
return kustomizev1.KustomizationNotReady(
kustomization,
kustomizev1.BuildFailedReason,
err.Error(),
), fmt.Errorf("kustomize build error: %s", string(output))
}
// apply kustomization
cmd = fmt.Sprintf("cd %s && kubectl apply -f %s.yaml", tmpDir, kustomization.GetName())
if kustomization.Spec.Prune != "" {
cmd = fmt.Sprintf("cd %s && kubectl apply -f %s.yaml --prune -l %s",
tmpDir, kustomization.GetName(), kustomization.Spec.Prune)
}
command = exec.CommandContext(ctx, "/bin/sh", "-c", cmd)
output, err = command.CombinedOutput()
if err != nil {
err = fmt.Errorf("kubectl apply error: %w", err)
return kustomizev1.KustomizationNotReady(
kustomization,
kustomizev1.ApplyFailedReason,
err.Error(),
), fmt.Errorf("kubectl apply: %s", string(output))
}
// log apply output
fmt.Println(string(output))
return kustomizev1.KustomizationReady(
kustomization,
kustomizev1.ApplySucceedReason,
"kustomization was successfully applied",
), nil
}