Skip to content

Commit

Permalink
feat: generate an app-scaffold files
Browse files Browse the repository at this point in the history
  • Loading branch information
mrsimonemms committed Jan 15, 2025
1 parent 2cce2ca commit 239dc5c
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 2 deletions.
20 changes: 18 additions & 2 deletions cmd/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ See the LICENSE file for more details.
package cmd

import (
"fmt"
"os"
"path/filepath"

"github.com/konstructio/kubefirst/internal/generate"
"github.com/konstructio/kubefirst/internal/progress"
"github.com/spf13/cobra"
)
Expand All @@ -26,19 +31,30 @@ func GenerateCommand() *cobra.Command {
func generateApp() *cobra.Command {
var name string
var environments []string
var outputPath string

appScaffoldCmd := &cobra.Command{
Use: "app-scaffold",
Short: "scaffold the gitops application repo",
TraverseChildren: true,
Run: func(_ *cobra.Command, _ []string) {
progress.Success("hello world")
RunE: func(_ *cobra.Command, _ []string) error {
if err := generate.AppScaffold(name, environments, outputPath); err != nil {
progress.Error(err.Error())
return err

Check failure on line 43 in cmd/generate.go

View workflow job for this annotation

GitHub Actions / build

error returned from external package is unwrapped: sig: func github.com/konstructio/kubefirst/internal/generate.AppScaffold(appName string, environments []string, outputPath string) error (wrapcheck)
}

progress.Success(fmt.Sprintf("App successfully scaffolded: %s", name))
return nil
},
}

cwd, err := os.Getwd()
cobra.CheckErr(err)

appScaffoldCmd.Flags().StringVarP(&name, "name", "n", "", "name of the app")
appScaffoldCmd.MarkFlagRequired("name")
appScaffoldCmd.Flags().StringSliceVar(&environments, "environments", []string{"development", "staging", "production"}, "environment names to create")
appScaffoldCmd.Flags().StringVar(&outputPath, "output-path", filepath.Join(cwd, "registry", "environments"), "location to save generated files")

return appScaffoldCmd
}
37 changes: 37 additions & 0 deletions internal/generate/files.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package generate

import (
"bytes"
"os"
"path/filepath"

"github.com/pkg/errors"
)

type Files struct {
data map[string]bytes.Buffer
}

func (f *Files) Add(file string, content bytes.Buffer) {
if f.data == nil {
f.data = map[string]bytes.Buffer{}
}

f.data[file] = content
}

func (f *Files) Save(filePrefix string) error {
for file, content := range f.data {
name := filepath.Join(filePrefix, file)

if err := os.MkdirAll(filepath.Dir(name), 0755); err != nil {
return errors.Wrap(err, "failed to create directory")
}

if err := os.WriteFile(name, content.Bytes(), 0644); err != nil {
return errors.Wrap(err, "failed to write file")
}
}

return nil
}
95 changes: 95 additions & 0 deletions internal/generate/scaffold.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package generate

import (
"bytes"
"embed"
"fmt"
"io/fs"
"path/filepath"
"strings"
"text/template"

"github.com/pkg/errors"
)

//go:embed scaffold
var scaffoldFS embed.FS

type ScaffoldData struct {
AppName string
DeploymentName string
Description string
Environment string
Namespace string
}

func AppScaffold(appName string, environments []string, outputPath string) error {
for _, env := range environments {
files, err := generateAppScaffoldEnvironmentFiles(appName, env)
if err != nil {
return err
}

if err := files.Save(filepath.Join(outputPath, env)); err != nil {
return err
}
}
return nil
}

func generateAppScaffoldEnvironmentFiles(appName, environment string) (*Files, error) {
tpl, err := template.New("tpl").ParseFS(scaffoldFS, "scaffold/*.yaml", "scaffold/**/*.yaml")
if err != nil {
return nil, errors.Wrap(err, "failed to create template")
}

data := ScaffoldData{
AppName: appName,
DeploymentName: fmt.Sprintf("%s-environment-%s", environment, appName),
Description: fmt.Sprintf("%s example application", appName),
Environment: environment,
Namespace: environment,
}

files := &Files{}
err = fs.WalkDir(scaffoldFS, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return errors.Wrap(err, "error walking directory")
}

if d.IsDir() {
return nil
}

// Get the file name without the root path
s := strings.Split(path, string(filepath.Separator))
file := strings.Join(s[1:], string(filepath.Separator))

// Parse any template variables in the file name
fileTpl, err := tpl.Parse(file)
if err != nil {
return errors.Wrap(err, "error parsing file name")
}

var fileNameOutput bytes.Buffer
if err := fileTpl.Execute(&fileNameOutput, data); err != nil {
return errors.Wrap(err, "error executing file name")
}

// Parse the contents of the file
var fileContent bytes.Buffer
if err := tpl.ExecuteTemplate(&fileContent, d.Name(), data); err != nil {
return errors.Wrap(err, "error executing template")
}

// Now store everything for output
files.Add(fileNameOutput.String(), fileContent)

return nil
})
if err != nil {
return nil, errors.Wrap(err, "error walking directory")
}

return files, nil
}
24 changes: 24 additions & 0 deletions internal/generate/scaffold/{{ .AppName }}.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: "{{ .DeploymentName }}"
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
annotations:
argocd.argoproj.io/sync-wave: '45'
spec:
project: default
source:
repoURL: <GITOPS_REPO_URL>
path: "registry/environments/{{ .Environment }}/{{ .AppName }}"
targetRevision: HEAD
destination:
name: in-cluster
namespace: "{{ .Namespace }}"
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
9 changes: 9 additions & 0 deletions internal/generate/scaffold/{{ .AppName }}/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
apiVersion: v2
description: "{{ .Description }}"
name: "{{ .AppName }}"
type: application
version: 1.0.0
dependencies:
- name: "{{ .AppName }}"
repository: http://chartmuseum.chartmuseum.svc.cluster.local:8080
version: 0.0.1-rc.awaiting-ci
29 changes: 29 additions & 0 deletions internal/generate/scaffold/{{ .AppName }}/values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# This is a generated file. These values may not correspond to your own chart's values

"{{ .AppName }}":
annotations: |
linkerd.io/inject: "enabled"
labels: |
mirror.linkerd.io/exported: "true"
image:
repository: "<CONTAINER_REGISTRY_URL>/{{ .AppName }}"
imagePullSecrets:
- name: docker-config
ingress:
className: nginx
enabled: true
annotations:
<CERT_MANAGER_ISSUER_ANNOTATION_1>
<CERT_MANAGER_ISSUER_ANNOTATION_2>
<CERT_MANAGER_ISSUER_ANNOTATION_3>
<CERT_MANAGER_ISSUER_ANNOTATION_4>
nginx.ingress.kubernetes.io/service-upstream: "true"
hosts:
- host: "{{ .AppName }}-{{ .Environment }}.<DOMAIN_NAME>"
paths:
- path: /
pathType: Prefix
tls:
- secretName: "{{ .AppName }}-tls"
hosts:
- "{{ .AppName }}-{{ .Environment }}.<DOMAIN_NAME>"

0 comments on commit 239dc5c

Please sign in to comment.