-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: install flux components API (#1656)
- Loading branch information
Showing
5 changed files
with
598 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
package flux | ||
|
||
import ( | ||
"bufio" | ||
"bytes" | ||
"context" | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
"time" | ||
|
||
corev1 "k8s.io/api/core/v1" | ||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | ||
"k8s.io/cli-runtime/pkg/genericclioptions" | ||
"k8s.io/client-go/rest" | ||
"sigs.k8s.io/cli-utils/pkg/kstatus/polling" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
"sigs.k8s.io/kustomize/api/konfig" | ||
|
||
"github.com/fluxcd/flux2/v2/pkg/manifestgen/kustomization" | ||
runclient "github.com/fluxcd/pkg/runtime/client" | ||
"github.com/fluxcd/pkg/ssa" | ||
|
||
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1" | ||
imageautov1 "github.com/fluxcd/image-automation-controller/api/v1beta1" | ||
imagereflectv1 "github.com/fluxcd/image-reflector-controller/api/v1beta2" | ||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" | ||
notificationv1 "github.com/fluxcd/notification-controller/api/v1" | ||
notificationv1b2 "github.com/fluxcd/notification-controller/api/v1beta2" | ||
sourcev1 "github.com/fluxcd/source-controller/api/v1" | ||
sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2" | ||
|
||
appsv1 "k8s.io/api/apps/v1" | ||
networkingv1 "k8s.io/api/networking/v1" | ||
rbacv1 "k8s.io/api/rbac/v1" | ||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" | ||
apiruntime "k8s.io/apimachinery/pkg/runtime" | ||
) | ||
|
||
// Apply is the equivalent of 'kubectl apply --server-side -f'. | ||
// If the given manifest is a kustomization.yaml, then apply performs the equivalent of 'kubectl apply --server-side -k'. | ||
func Apply(ctx context.Context, rcg genericclioptions.RESTClientGetter, opts *runclient.Options, root, manifestPath string) (string, error) { | ||
objs, err := readObjects(root, manifestPath) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
if len(objs) == 0 { | ||
return "", fmt.Errorf("no Kubernetes objects found at: %s", manifestPath) | ||
} | ||
|
||
if err := ssa.SetNativeKindsDefaults(objs); err != nil { | ||
return "", err | ||
} | ||
|
||
changeSet := ssa.NewChangeSet() | ||
|
||
// contains only CRDs and Namespaces | ||
var stageOne []*unstructured.Unstructured | ||
|
||
// contains all objects except for CRDs and Namespaces | ||
var stageTwo []*unstructured.Unstructured | ||
|
||
for _, u := range objs { | ||
if ssa.IsClusterDefinition(u) { | ||
stageOne = append(stageOne, u) | ||
} else { | ||
stageTwo = append(stageTwo, u) | ||
} | ||
} | ||
|
||
if len(stageOne) > 0 { | ||
cs, err := applySet(ctx, rcg, opts, stageOne) | ||
if err != nil { | ||
return "", err | ||
} | ||
changeSet.Append(cs.Entries) | ||
} | ||
|
||
if err := waitForSet(rcg, opts, changeSet); err != nil { | ||
return "", err | ||
} | ||
|
||
if len(stageTwo) > 0 { | ||
cs, err := applySet(ctx, rcg, opts, stageTwo) | ||
if err != nil { | ||
return "", err | ||
} | ||
changeSet.Append(cs.Entries) | ||
} | ||
|
||
return changeSet.String(), nil | ||
} | ||
|
||
func readObjects(root, manifestPath string) ([]*unstructured.Unstructured, error) { | ||
fi, err := os.Lstat(manifestPath) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if fi.IsDir() || !fi.Mode().IsRegular() { | ||
return nil, fmt.Errorf("expected %q to be a file", manifestPath) | ||
} | ||
|
||
if isRecognizedKustomizationFile(manifestPath) { | ||
resources, err := kustomization.BuildWithRoot(root, filepath.Dir(manifestPath)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return ssa.ReadObjects(bytes.NewReader(resources)) | ||
} | ||
|
||
ms, err := os.Open(manifestPath) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer ms.Close() | ||
|
||
return ssa.ReadObjects(bufio.NewReader(ms)) | ||
} | ||
|
||
func newManager(rcg genericclioptions.RESTClientGetter, opts *runclient.Options) (*ssa.ResourceManager, error) { | ||
cfg, err := KubeConfig(rcg, opts) | ||
if err != nil { | ||
return nil, err | ||
} | ||
restMapper, err := rcg.ToRESTMapper() | ||
if err != nil { | ||
return nil, err | ||
} | ||
kubeClient, err := client.New(cfg, client.Options{Mapper: restMapper, Scheme: NewScheme()}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
kubePoller := polling.NewStatusPoller(kubeClient, restMapper, polling.Options{}) | ||
|
||
return ssa.NewResourceManager(kubeClient, kubePoller, ssa.Owner{ | ||
Field: "flux", | ||
Group: "fluxcd.io", | ||
}), nil | ||
} | ||
|
||
// Create the Scheme, methods for serializing and deserializing API objects | ||
// which can be shared by tests. | ||
func NewScheme() *apiruntime.Scheme { | ||
scheme := apiruntime.NewScheme() | ||
_ = apiextensionsv1.AddToScheme(scheme) | ||
_ = corev1.AddToScheme(scheme) | ||
_ = rbacv1.AddToScheme(scheme) | ||
_ = appsv1.AddToScheme(scheme) | ||
_ = networkingv1.AddToScheme(scheme) | ||
_ = sourcev1b2.AddToScheme(scheme) | ||
_ = sourcev1.AddToScheme(scheme) | ||
_ = kustomizev1.AddToScheme(scheme) | ||
_ = helmv2.AddToScheme(scheme) | ||
_ = notificationv1.AddToScheme(scheme) | ||
_ = notificationv1b2.AddToScheme(scheme) | ||
_ = imagereflectv1.AddToScheme(scheme) | ||
_ = imageautov1.AddToScheme(scheme) | ||
return scheme | ||
} | ||
|
||
func KubeConfig(rcg genericclioptions.RESTClientGetter, opts *runclient.Options) (*rest.Config, error) { | ||
cfg, err := rcg.ToRESTConfig() | ||
if err != nil { | ||
return nil, fmt.Errorf("kubernetes configuration load failed: %w", err) | ||
} | ||
|
||
// avoid throttling request when some Flux CRDs are not registered | ||
cfg.QPS = opts.QPS | ||
cfg.Burst = opts.Burst | ||
|
||
return cfg, nil | ||
} | ||
|
||
func applySet(ctx context.Context, rcg genericclioptions.RESTClientGetter, opts *runclient.Options, objects []*unstructured.Unstructured) (*ssa.ChangeSet, error) { | ||
man, err := newManager(rcg, opts) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return man.ApplyAll(ctx, objects, ssa.DefaultApplyOptions()) | ||
} | ||
|
||
func waitForSet(rcg genericclioptions.RESTClientGetter, opts *runclient.Options, changeSet *ssa.ChangeSet) error { | ||
man, err := newManager(rcg, opts) | ||
if err != nil { | ||
return err | ||
} | ||
return man.WaitForSet(changeSet.ToObjMetadataSet(), ssa.WaitOptions{Interval: 2 * time.Second, Timeout: time.Minute}) | ||
} | ||
|
||
func isRecognizedKustomizationFile(path string) bool { | ||
base := filepath.Base(path) | ||
for _, v := range konfig.RecognizedKustomizationFileNames() { | ||
if base == v { | ||
return true | ||
} | ||
} | ||
return false | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package flux | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/fluxcd/flux2/v2/pkg/manifestgen" | ||
"github.com/fluxcd/flux2/v2/pkg/manifestgen/install" | ||
runclient "github.com/fluxcd/pkg/runtime/client" | ||
"k8s.io/cli-runtime/pkg/genericclioptions" | ||
) | ||
|
||
type Options struct { | ||
KubeconfigArgs *genericclioptions.ConfigFlags | ||
KubeclientOptions *runclient.Options | ||
Namespace string | ||
Components []string | ||
} | ||
|
||
// Install installs flux components in the given namespace on the cluster using the given kubeconfig and client options. | ||
func Install(ctx context.Context, opts Options) error { | ||
select { | ||
case <-ctx.Done(): | ||
return ctx.Err() | ||
default: | ||
} | ||
|
||
// make default options for installing flux components | ||
options := install.MakeDefaultOptions() | ||
options.Namespace = opts.Namespace | ||
options.Components = opts.Components | ||
|
||
// generate flux manifest | ||
manifest, err := install.Generate(options, "") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// create a temporary directory for the manifest | ||
tmpDir, err := manifestgen.MkdirTempAbs("", opts.Namespace) | ||
if err != nil { | ||
return err | ||
} | ||
defer os.RemoveAll(tmpDir) | ||
|
||
// write the manifest to the temporary directory | ||
if _, err := manifest.WriteFile(tmpDir); err != nil { | ||
return fmt.Errorf("install failed: %w", err) | ||
} | ||
|
||
_, err = Apply( | ||
ctx, | ||
opts.KubeconfigArgs, | ||
opts.KubeclientOptions, | ||
tmpDir, | ||
filepath.Join(tmpDir, manifest.Path)) | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.