diff --git a/pkg/cmd/create/root.go b/pkg/cmd/create/root.go index a723dfc4..5b16eed8 100644 --- a/pkg/cmd/create/root.go +++ b/pkg/cmd/create/root.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "net/url" - "os" "path/filepath" "strings" @@ -58,7 +57,7 @@ func init() { CreateCmd.PersistentFlags().StringVar(&port, "port", "8443", "Port number under which idpBuilder tools are accessible.") CreateCmd.PersistentFlags().BoolVar(&pathRouting, "use-path-routing", false, "When set to true, web UIs are exposed under single domain name.") CreateCmd.Flags().StringSliceVarP(&extraPackagesDirs, "package-dir", "p", []string{}, "Paths to custom packages") - CreateCmd.Flags().StringSliceVarP(&packageCustomizationFiles, "package-custom-file", "c", []string{}, "name of the package and the path to file to customize the package with. e.g. argocd:/tmp/argocd.yaml") + CreateCmd.Flags().StringSliceVarP(&packageCustomizationFiles, "package-custom-file", "c", []string{}, "Name of the package and the path to file to customize the package with. e.g. argocd:/tmp/argocd.yaml") // idpbuilder related flags CreateCmd.Flags().BoolVarP(&noExit, "no-exit", "n", true, "When set, idpbuilder will not exit after all packages are synced. Useful for continuously syncing local directories.") } @@ -86,7 +85,7 @@ func create(cmd *cobra.Command, args []string) error { var absDirPaths []string if len(extraPackagesDirs) > 0 { - p, err := getAbsFilePaths(extraPackagesDirs, true) + p, err := helpers.GetAbsFilePaths(extraPackagesDirs, true) if err != nil { return err } @@ -95,7 +94,7 @@ func create(cmd *cobra.Command, args []string) error { o := make(map[string]v1alpha1.PackageCustomization) for i := range packageCustomizationFiles { - c, pErr := validatePackageCustomFile(packageCustomizationFiles[i]) + c, pErr := getPackageCustomFile(packageCustomizationFiles[i]) if pErr != nil { return pErr } @@ -141,7 +140,7 @@ func validate() error { } for i := range packageCustomizationFiles { - _, pErr := validatePackageCustomFile(packageCustomizationFiles[i]) + _, pErr := getPackageCustomFile(packageCustomizationFiles[i]) if pErr != nil { return pErr } @@ -149,26 +148,21 @@ func validate() error { return nil } -func validatePackageCustomFile(input string) (v1alpha1.PackageCustomization, error) { +func getPackageCustomFile(input string) (v1alpha1.PackageCustomization, error) { // the format should be `:` s := strings.Split(input, ":") - if len(s) == 2 { + if len(s) != 2 { return v1alpha1.PackageCustomization{}, fmt.Errorf("ensure %s is formated as :", input) } - filePath := s[1] - f, err := filepath.Abs(filePath) + paths, err := helpers.GetAbsFilePaths([]string{s[1]}, false) if err != nil { - - return v1alpha1.PackageCustomization{}, fmt.Errorf("ensure file path (%s) is correct: %w", filePath, err) + return v1alpha1.PackageCustomization{}, err } - info, err := os.Stat(f) + err = helpers.ValidateKubernetesYamlFile(paths[0]) if err != nil { - return v1alpha1.PackageCustomization{}, fmt.Errorf("ensure file (%s) exists and readable: %w", f, err) - } - if !info.Mode().IsRegular() { - return v1alpha1.PackageCustomization{}, fmt.Errorf("ensure specified file (%s) is a readable file", f) + return v1alpha1.PackageCustomization{}, err } corePkgs := map[string]struct{}{"argocd": {}, "gitea": {}, "nginx": {}} @@ -179,35 +173,6 @@ func validatePackageCustomFile(input string) (v1alpha1.PackageCustomization, err } return v1alpha1.PackageCustomization{ Name: name, - FilePath: f, + FilePath: paths[0], }, nil } - -//func validateYaml(absPath string) error { -// -//} - -func getAbsFilePaths(paths []string, isDir bool) ([]string, error) { - out := make([]string, len(paths)) - for i := range paths { - path := paths[i] - absPath, err := filepath.Abs(path) - if err != nil { - return nil, fmt.Errorf("failed to validate path %s : %w", path, err) - } - f, err := os.Stat(absPath) - if err != nil { - return nil, fmt.Errorf("failed to validate path %s : %w", absPath, err) - } - if isDir && !f.IsDir() { - return nil, fmt.Errorf("given path is not a directory. %s", absPath) - } - if !isDir && !f.Mode().IsRegular() { - return nil, fmt.Errorf("give path is not a file. %s", absPath) - } - - out[i] = absPath - } - - return out, nil -} diff --git a/pkg/cmd/helpers/test-data/notk8s.yaml b/pkg/cmd/helpers/test-data/notk8s.yaml new file mode 100644 index 00000000..0ff60374 --- /dev/null +++ b/pkg/cmd/helpers/test-data/notk8s.yaml @@ -0,0 +1,2 @@ +name: me +pluto: not a planet diff --git a/pkg/cmd/helpers/test-data/notyaml.yaml b/pkg/cmd/helpers/test-data/notyaml.yaml new file mode 100644 index 00000000..9445836f --- /dev/null +++ b/pkg/cmd/helpers/test-data/notyaml.yaml @@ -0,0 +1,3 @@ +not: +a +yaml diff --git a/pkg/cmd/helpers/test-data/valid.yaml b/pkg/cmd/helpers/test-data/valid.yaml new file mode 100644 index 00000000..c2910ced --- /dev/null +++ b/pkg/cmd/helpers/test-data/valid.yaml @@ -0,0 +1,47 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + labels: + app: nginx +spec: + replicas: 3 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 +--- +apiVersion: v1 +kind: PersistentVolume +metadata: + name: my-pv +spec: + capacity: + storage: 1Gi + accessModes: + - ReadWriteOnce + storageClassName: standard + hostPath: + path: /mnt/data +--- +apiVersion: v1 +kind: Service +metadata: + name: nginx-service +spec: + selector: + app: nginx + ports: + - protocol: TCP + port: 80 + targetPort: 80 + diff --git a/pkg/cmd/helpers/validation.go b/pkg/cmd/helpers/validation.go new file mode 100644 index 00000000..ef77e7d5 --- /dev/null +++ b/pkg/cmd/helpers/validation.go @@ -0,0 +1,60 @@ +package helpers + +import ( + "fmt" + "os" + "path/filepath" + + "sigs.k8s.io/kustomize/kyaml/kio" +) + +func ValidateKubernetesYamlFile(absPath string) error { + if !filepath.IsAbs(absPath) { + return fmt.Errorf("given path is not an absolute path %s", absPath) + } + b, err := os.ReadFile(absPath) + if err != nil { + return fmt.Errorf("failed reading file: %s, err: %w", absPath, err) + } + n, err := kio.FromBytes(b) + if err != nil { + return fmt.Errorf("failed parsing file as kubernetes manifests file: %s, err: %w", absPath, err) + } + + for i := range n { + obj := n[i] + if obj.IsNilOrEmpty() { + return fmt.Errorf("given file %s contains an invalid kubenretes manifest", absPath) + } + if obj.GetKind() == "" || obj.GetApiVersion() == "" { + return fmt.Errorf("given file %s contains an invalid kubenretes manifest", absPath) + } + } + + return nil +} + +func GetAbsFilePaths(paths []string, isDir bool) ([]string, error) { + out := make([]string, len(paths)) + for i := range paths { + path := paths[i] + absPath, err := filepath.Abs(path) + if err != nil { + return nil, fmt.Errorf("failed to validate path %s : %w", path, err) + } + f, err := os.Stat(absPath) + if err != nil { + return nil, fmt.Errorf("failed to validate path %s : %w", absPath, err) + } + if isDir && !f.IsDir() { + return nil, fmt.Errorf("given path is not a directory. %s", absPath) + } + if !isDir && !f.Mode().IsRegular() { + return nil, fmt.Errorf("give path is not a file. %s", absPath) + } + + out[i] = absPath + } + + return out, nil +} diff --git a/pkg/cmd/helpers/validation_test.go b/pkg/cmd/helpers/validation_test.go new file mode 100644 index 00000000..32d5c465 --- /dev/null +++ b/pkg/cmd/helpers/validation_test.go @@ -0,0 +1,35 @@ +package helpers + +import ( + "fmt" + "os" + "testing" +) + +func TestValidateKubernetesYaml(t *testing.T) { + cwd, err := os.Getwd() + if err != nil { + t.Fatalf("could not get current working directory") + } + + cases := map[string]struct { + expectErr bool + inputPath string + }{ + //"invalidPath": {expectErr: true, inputPath: fmt.Sprintf("%s/invalid/path", cwd)}, + //"notAbs": {expectErr: true, inputPath: fmt.Sprintf("invalid/path")}, + //"valid": {expectErr: false, inputPath: fmt.Sprintf("%s/test-data/valid.yaml", cwd)}, + //"notYaml": {expectErr: true, inputPath: fmt.Sprintf("%s/test-data/notyaml.yaml", cwd)}, + "notk8s": {expectErr: true, inputPath: fmt.Sprintf("%s/test-data/notk8s.yaml", cwd)}, + } + + for k := range cases { + cErr := ValidateKubernetesYamlFile(cases[k].inputPath) + if cases[k].expectErr && cErr == nil { + t.Fatalf("%s expected error but did not receive error", k) + } + if !cases[k].expectErr && cErr != nil { + t.Fatalf("%s did not expect error but received error", k) + } + } +} diff --git a/pkg/controllers/localbuild/argo.go b/pkg/controllers/localbuild/argo.go index 756c9309..b912a772 100644 --- a/pkg/controllers/localbuild/argo.go +++ b/pkg/controllers/localbuild/argo.go @@ -5,7 +5,7 @@ import ( "embed" "github.com/cnoe-io/idpbuilder/api/v1alpha1" - "github.com/cnoe-io/idpbuilder/pkg/util" + "github.com/cnoe-io/idpbuilder/pkg/k8s" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ctrl "sigs.k8s.io/controller-runtime" @@ -19,7 +19,7 @@ const ( ) func RawArgocdInstallResources(templateData any, config v1alpha1.PackageCustomization, scheme *runtime.Scheme) ([][]byte, error) { - return util.BuildCustomizedManifests(config.FilePath, "resources/argo", installArgoFS, scheme, templateData) + return k8s.BuildCustomizedManifests(config.FilePath, "resources/argo", installArgoFS, scheme, templateData) } func (r *LocalbuildReconciler) ReconcileArgo(ctx context.Context, req ctrl.Request, resource *v1alpha1.Localbuild) (ctrl.Result, error) { diff --git a/pkg/controllers/localbuild/gitea.go b/pkg/controllers/localbuild/gitea.go index 2d084d14..7f105a99 100644 --- a/pkg/controllers/localbuild/gitea.go +++ b/pkg/controllers/localbuild/gitea.go @@ -6,7 +6,7 @@ import ( "fmt" "github.com/cnoe-io/idpbuilder/api/v1alpha1" - "github.com/cnoe-io/idpbuilder/pkg/util" + "github.com/cnoe-io/idpbuilder/pkg/k8s" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ctrl "sigs.k8s.io/controller-runtime" @@ -27,7 +27,7 @@ const ( var installGiteaFS embed.FS func RawGiteaInstallResources(templateData any, config v1alpha1.PackageCustomization, scheme *runtime.Scheme) ([][]byte, error) { - return util.BuildCustomizedManifests(config.FilePath, "resources/gitea/k8s", installGiteaFS, scheme, templateData) + return k8s.BuildCustomizedManifests(config.FilePath, "resources/gitea/k8s", installGiteaFS, scheme, templateData) } func (r *LocalbuildReconciler) ReconcileGitea(ctx context.Context, req ctrl.Request, resource *v1alpha1.Localbuild) (ctrl.Result, error) { diff --git a/pkg/controllers/localbuild/installer.go b/pkg/controllers/localbuild/installer.go index e70d1dbe..c2780796 100644 --- a/pkg/controllers/localbuild/installer.go +++ b/pkg/controllers/localbuild/installer.go @@ -40,7 +40,7 @@ type EmbeddedInstallation struct { } func (e *EmbeddedInstallation) installResources(scheme *runtime.Scheme, templateData any) ([]client.Object, error) { - return util.BuildCustomizedObjects(e.customization.FilePath, e.resourcePath, e.resourceFS, scheme, templateData) + return k8s.BuildCustomizedObjects(e.customization.FilePath, e.resourcePath, e.resourceFS, scheme, templateData) } func (e *EmbeddedInstallation) newNamespace(namespace string) *corev1.Namespace { diff --git a/pkg/controllers/localbuild/nginx.go b/pkg/controllers/localbuild/nginx.go index cec33950..a8c6d9c3 100644 --- a/pkg/controllers/localbuild/nginx.go +++ b/pkg/controllers/localbuild/nginx.go @@ -5,7 +5,7 @@ import ( "embed" "github.com/cnoe-io/idpbuilder/api/v1alpha1" - "github.com/cnoe-io/idpbuilder/pkg/util" + "github.com/cnoe-io/idpbuilder/pkg/k8s" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ctrl "sigs.k8s.io/controller-runtime" @@ -19,7 +19,7 @@ const ( var installNginxFS embed.FS func RawNginxInstallResources(templateData any, config v1alpha1.PackageCustomization, scheme *runtime.Scheme) ([][]byte, error) { - return util.BuildCustomizedManifests(config.FilePath, "resources/nginx/k8s", installNginxFS, scheme, templateData) + return k8s.BuildCustomizedManifests(config.FilePath, "resources/nginx/k8s", installNginxFS, scheme, templateData) } func (r *LocalbuildReconciler) ReconcileNginx(ctx context.Context, req ctrl.Request, resource *v1alpha1.Localbuild) (ctrl.Result, error) { diff --git a/pkg/util/test-resources/input/argocd-cm.yaml b/pkg/k8s/test-resources/input/argocd-cm.yaml similarity index 100% rename from pkg/util/test-resources/input/argocd-cm.yaml rename to pkg/k8s/test-resources/input/argocd-cm.yaml diff --git a/pkg/util/test-resources/input/argocd/install.yaml b/pkg/k8s/test-resources/input/argocd/install.yaml similarity index 100% rename from pkg/util/test-resources/input/argocd/install.yaml rename to pkg/k8s/test-resources/input/argocd/install.yaml diff --git a/pkg/util/test-resources/input/extra.yaml b/pkg/k8s/test-resources/input/extra.yaml similarity index 100% rename from pkg/util/test-resources/input/extra.yaml rename to pkg/k8s/test-resources/input/extra.yaml diff --git a/pkg/util/test-resources/input/extra.yaml.tmpl b/pkg/k8s/test-resources/input/extra.yaml.tmpl similarity index 100% rename from pkg/util/test-resources/input/extra.yaml.tmpl rename to pkg/k8s/test-resources/input/extra.yaml.tmpl diff --git a/pkg/util/test-resources/input/nginx/install.yaml b/pkg/k8s/test-resources/input/nginx/install.yaml similarity index 100% rename from pkg/util/test-resources/input/nginx/install.yaml rename to pkg/k8s/test-resources/input/nginx/install.yaml diff --git a/pkg/util/test-resources/output/argocd/install.yaml b/pkg/k8s/test-resources/output/argocd/install.yaml similarity index 100% rename from pkg/util/test-resources/output/argocd/install.yaml rename to pkg/k8s/test-resources/output/argocd/install.yaml diff --git a/pkg/util/test-resources/output/nginx/install-tmpl.yaml b/pkg/k8s/test-resources/output/nginx/install-tmpl.yaml similarity index 100% rename from pkg/util/test-resources/output/nginx/install-tmpl.yaml rename to pkg/k8s/test-resources/output/nginx/install-tmpl.yaml diff --git a/pkg/util/test-resources/output/nginx/install.yaml b/pkg/k8s/test-resources/output/nginx/install.yaml similarity index 100% rename from pkg/util/test-resources/output/nginx/install.yaml rename to pkg/k8s/test-resources/output/nginx/install.yaml diff --git a/pkg/k8s/util.go b/pkg/k8s/util.go index 01d667c3..df40195a 100644 --- a/pkg/k8s/util.go +++ b/pkg/k8s/util.go @@ -1 +1,60 @@ package k8s + +import ( + "embed" + "os" + + "github.com/cnoe-io/idpbuilder/pkg/util" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func BuildCustomizedManifests(filePath, fsPath string, resourceFS embed.FS, scheme *runtime.Scheme, templateData any) ([][]byte, error) { + rawResources, err := util.ConvertFSToBytes(resourceFS, fsPath, templateData) + if err != nil { + return nil, err + } + + if filePath == "" { + return rawResources, nil + } + + bs, _, err := applyOverrides(filePath, rawResources, scheme, templateData) + if err != nil { + return nil, err + } + + return bs, nil +} + +func BuildCustomizedObjects(filePath, fsPath string, resourceFS embed.FS, scheme *runtime.Scheme, templateData any) ([]client.Object, error) { + rawResources, err := util.ConvertFSToBytes(resourceFS, fsPath, templateData) + if err != nil { + return nil, err + } + + if filePath == "" { + return ConvertRawResourcesToObjects(scheme, rawResources) + } + + _, objs, err := applyOverrides(filePath, rawResources, scheme, templateData) + if err != nil { + return nil, err + } + + return objs, nil +} + +func applyOverrides(filePath string, originalFiles [][]byte, scheme *runtime.Scheme, templateData any) ([][]byte, []client.Object, error) { + customBS, err := os.ReadFile(filePath) + if err != nil { + return nil, nil, err + } + + rendered, err := util.ApplyTemplate(customBS, templateData) + if err != nil { + return nil, nil, err + } + + return ConvertYamlToObjectsWithOverride(scheme, originalFiles, rendered) +} diff --git a/pkg/util/files_test.go b/pkg/k8s/util_test.go similarity index 94% rename from pkg/util/files_test.go rename to pkg/k8s/util_test.go index 2cd92f0d..217f8dec 100644 --- a/pkg/util/files_test.go +++ b/pkg/k8s/util_test.go @@ -1,4 +1,4 @@ -package util +package k8s import ( "bytes" @@ -6,7 +6,7 @@ import ( "os" "testing" - "github.com/cnoe-io/idpbuilder/pkg/k8s" + "github.com/cnoe-io/idpbuilder/pkg/util" "github.com/stretchr/testify/assert" ) @@ -38,7 +38,7 @@ func TestBuildCustomizedManifests(t *testing.T) { for key := range cases { c := cases[key] - b, err := BuildCustomizedManifests(c.filePath, c.fsPath, testDataFS, k8s.GetScheme(), CorePackageTemplateConfig{ + b, err := BuildCustomizedManifests(c.filePath, c.fsPath, testDataFS, GetScheme(), util.CorePackageTemplateConfig{ Protocol: "http", Host: "cnoe.localtest.me", IngressHost: "localhost", diff --git a/pkg/util/files.go b/pkg/util/files.go index 82da8fd2..5a5cdf5c 100644 --- a/pkg/util/files.go +++ b/pkg/util/files.go @@ -2,16 +2,11 @@ package util import ( "bytes" - "embed" "fmt" "io" "os" "path/filepath" "text/template" - - "github.com/cnoe-io/idpbuilder/pkg/k8s" - "k8s.io/apimachinery/pkg/runtime" - "sigs.k8s.io/controller-runtime/pkg/client" ) func CopyDirectory(scrDir, dest string) error { @@ -113,53 +108,3 @@ func ApplyTemplate(in []byte, templateData any) ([]byte, error) { return ret.Bytes(), nil } - -func BuildCustomizedManifests(filePath, fsPath string, resourceFS embed.FS, scheme *runtime.Scheme, templateData any) ([][]byte, error) { - rawResources, err := ConvertFSToBytes(resourceFS, fsPath, templateData) - if err != nil { - return nil, err - } - - if filePath == "" { - return rawResources, nil - } - - bs, _, err := applyOverrides(filePath, rawResources, scheme, templateData) - if err != nil { - return nil, err - } - - return bs, nil -} - -func BuildCustomizedObjects(filePath, fsPath string, resourceFS embed.FS, scheme *runtime.Scheme, templateData any) ([]client.Object, error) { - rawResources, err := ConvertFSToBytes(resourceFS, fsPath, templateData) - if err != nil { - return nil, err - } - - if filePath == "" { - return k8s.ConvertRawResourcesToObjects(scheme, rawResources) - } - - _, objs, err := applyOverrides(filePath, rawResources, scheme, templateData) - if err != nil { - return nil, err - } - - return objs, nil -} - -func applyOverrides(filePath string, originalFiles [][]byte, scheme *runtime.Scheme, templateData any) ([][]byte, []client.Object, error) { - customBS, err := os.ReadFile(filePath) - if err != nil { - return nil, nil, err - } - - rendered, err := ApplyTemplate(customBS, templateData) - if err != nil { - return nil, nil, err - } - - return k8s.ConvertYamlToObjectsWithOverride(scheme, originalFiles, rendered) -} diff --git a/pkg/util/util.go b/pkg/util/util.go index b0a4011c..59099369 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -3,6 +3,7 @@ package util import ( "context" "fmt" + "github.com/cnoe-io/idpbuilder/api/v1alpha1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "sigs.k8s.io/controller-runtime/pkg/client"