github.com/argoproj/argo-cd/v3@v3.2.1/hack/gen-catalog/main.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path"
     9  	"path/filepath"
    10  	"strings"
    11  
    12  	"github.com/argoproj/notifications-engine/pkg/services"
    13  	"github.com/argoproj/notifications-engine/pkg/triggers"
    14  	"github.com/argoproj/notifications-engine/pkg/util/misc"
    15  	"github.com/olekukonko/tablewriter"
    16  	"github.com/olekukonko/tablewriter/renderer"
    17  	"github.com/olekukonko/tablewriter/tw"
    18  	log "github.com/sirupsen/logrus"
    19  	"github.com/spf13/cobra"
    20  	"github.com/spf13/cobra/doc"
    21  	corev1 "k8s.io/api/core/v1"
    22  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    23  	"sigs.k8s.io/yaml"
    24  
    25  	"github.com/argoproj/argo-cd/v3/cmd/argocd/commands/admin"
    26  )
    27  
    28  func main() {
    29  	command := &cobra.Command{
    30  		Use: "gen",
    31  		Run: func(c *cobra.Command, args []string) {
    32  			c.HelpFunc()(c, args)
    33  		},
    34  	}
    35  	command.AddCommand(newDocsCommand())
    36  	command.AddCommand(newCatalogCommand())
    37  
    38  	if err := command.Execute(); err != nil {
    39  		fmt.Println(err)
    40  		os.Exit(1)
    41  	}
    42  }
    43  
    44  func newCatalogCommand() *cobra.Command {
    45  	return &cobra.Command{
    46  		Use: "catalog",
    47  		Run: func(_ *cobra.Command, _ []string) {
    48  			cm := corev1.ConfigMap{
    49  				TypeMeta: metav1.TypeMeta{
    50  					Kind:       "ConfigMap",
    51  					APIVersion: "v1",
    52  				},
    53  				ObjectMeta: metav1.ObjectMeta{
    54  					Name: "argocd-notifications-cm",
    55  				},
    56  				Data: make(map[string]string),
    57  			}
    58  			wd, err := os.Getwd()
    59  			dieOnError(err, "Failed to get current working directory")
    60  			target := path.Join(wd, "notifications_catalog/install.yaml")
    61  
    62  			templatesDir := path.Join(wd, "notifications_catalog/templates")
    63  			triggersDir := path.Join(wd, "notifications_catalog/triggers")
    64  
    65  			templates, triggers, err := buildConfigFromFS(templatesDir, triggersDir)
    66  			dieOnError(err, "Failed to build catalog config")
    67  
    68  			misc.IterateStringKeyMap(triggers, func(name string) {
    69  				trigger := triggers[name]
    70  				t, err := yaml.Marshal(trigger)
    71  				dieOnError(err, "Failed to marshal trigger")
    72  				cm.Data["trigger."+name] = string(t)
    73  			})
    74  
    75  			misc.IterateStringKeyMap(templates, func(name string) {
    76  				template := templates[name]
    77  				t, err := yaml.Marshal(template)
    78  				dieOnError(err, "Failed to marshal template")
    79  				cm.Data["template."+name] = string(t)
    80  			})
    81  
    82  			d, err := yaml.Marshal(cm)
    83  			dieOnError(err, "Failed to marshal final configmap")
    84  
    85  			err = os.WriteFile(target, d, 0o644)
    86  			dieOnError(err, "Failed to write builtin configmap")
    87  		},
    88  	}
    89  }
    90  
    91  func newDocsCommand() *cobra.Command {
    92  	return &cobra.Command{
    93  		Use: "docs",
    94  		Run: func(_ *cobra.Command, _ []string) {
    95  			var builtItDocsData bytes.Buffer
    96  			wd, err := os.Getwd()
    97  			dieOnError(err, "Failed to get current working directory")
    98  
    99  			templatesDir := path.Join(wd, "notifications_catalog/templates")
   100  			triggersDir := path.Join(wd, "notifications_catalog/triggers")
   101  
   102  			notificationTemplates, notificationTriggers, err := buildConfigFromFS(templatesDir, triggersDir)
   103  			dieOnError(err, "Failed to build builtin config")
   104  			generateBuiltInTriggersDocs(&builtItDocsData, notificationTriggers, notificationTemplates)
   105  			if err := os.WriteFile("./docs/operator-manual/notifications/catalog.md", builtItDocsData.Bytes(), 0o644); err != nil {
   106  				log.Fatal(err)
   107  			}
   108  			var commandDocs bytes.Buffer
   109  			if err := generateCommandsDocs(&commandDocs); err != nil {
   110  				log.Fatal(err)
   111  			}
   112  			if err := os.WriteFile("./docs/operator-manual/notifications/troubleshooting-commands.md", commandDocs.Bytes(), 0o644); err != nil {
   113  				log.Fatal(err)
   114  			}
   115  		},
   116  	}
   117  }
   118  
   119  func generateBuiltInTriggersDocs(out io.Writer, triggers map[string][]triggers.Condition, templates map[string]services.Notification) {
   120  	_, _ = fmt.Fprintln(out, "# Triggers and Templates Catalog")
   121  
   122  	_, _ = fmt.Fprintln(out, "## Getting Started")
   123  	_, _ = fmt.Fprintln(out, "* Install Triggers and Templates from the catalog")
   124  	_, _ = fmt.Fprintln(out, "  ```bash")
   125  	_, _ = fmt.Fprintln(out, "  kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/notifications_catalog/install.yaml")
   126  	_, _ = fmt.Fprintln(out, "  ```")
   127  
   128  	_, _ = fmt.Fprintln(out, "## Triggers")
   129  
   130  	r := tw.Rendition{
   131  		Borders: tw.Border{Left: tw.On, Top: tw.Off, Right: tw.On, Bottom: tw.Off},
   132  		Symbols: tw.NewSymbolCustom("pipe-center").WithCenter("|").WithMidLeft("|").WithMidRight("|"),
   133  	}
   134  	c := tablewriter.NewConfigBuilder().WithRowAutoWrap(tw.WrapNone).Build()
   135  	table := tablewriter.NewTable(out, tablewriter.WithConfig(c), tablewriter.WithRenderer(renderer.NewBlueprint(r)))
   136  	table.Header("NAME", "DESCRIPTION", "TEMPLATE")
   137  	misc.IterateStringKeyMap(triggers, func(name string) {
   138  		t := triggers[name]
   139  		desc := ""
   140  		template := ""
   141  		if len(t) > 0 {
   142  			desc = t[0].Description
   143  			template = strings.Join(t[0].Send, ",")
   144  		}
   145  		err := table.Append([]string{name, desc, fmt.Sprintf("[%s](#%s)", template, template)})
   146  		if err != nil {
   147  			panic(err)
   148  		}
   149  	})
   150  	err := table.Render()
   151  	if err != nil {
   152  		panic(err)
   153  	}
   154  
   155  	_, _ = fmt.Fprintln(out, "")
   156  	_, _ = fmt.Fprintln(out, "## Templates")
   157  	misc.IterateStringKeyMap(templates, func(name string) {
   158  		t := templates[name]
   159  		yamlData, err := yaml.Marshal(t)
   160  		if err != nil {
   161  			panic(err)
   162  		}
   163  		_, _ = fmt.Fprintf(out, "### %s\n**definition**:\n```yaml\n%s\n```\n", name, string(yamlData))
   164  	})
   165  }
   166  
   167  func generateCommandsDocs(out io.Writer) error {
   168  	// create parents so that CommandPath() is correctly resolved
   169  	mainCmd := &cobra.Command{Use: "argocd"}
   170  	adminCmd := &cobra.Command{Use: "admin"}
   171  	toolCmd := admin.NewNotificationsCommand()
   172  	adminCmd.AddCommand(toolCmd)
   173  	mainCmd.AddCommand(adminCmd)
   174  	for _, mainSubCommand := range mainCmd.Commands() {
   175  		for _, adminSubCommand := range mainSubCommand.Commands() {
   176  			for _, toolSubCommand := range adminSubCommand.Commands() {
   177  				for _, c := range toolSubCommand.Commands() {
   178  					var cmdDesc bytes.Buffer
   179  					if err := doc.GenMarkdown(c, &cmdDesc); err != nil {
   180  						return fmt.Errorf("error generating Markdown for command: %v : %w", c, err)
   181  					}
   182  					for _, line := range strings.Split(cmdDesc.String(), "\n") {
   183  						if strings.HasPrefix(line, "### SEE ALSO") {
   184  							break
   185  						}
   186  						_, _ = fmt.Fprintf(out, "%s\n", line)
   187  					}
   188  				}
   189  			}
   190  		}
   191  	}
   192  	return nil
   193  }
   194  
   195  func dieOnError(err error, msg string) {
   196  	if err != nil {
   197  		fmt.Printf("[ERROR] %s: %v", msg, err)
   198  		os.Exit(1)
   199  	}
   200  }
   201  
   202  func buildConfigFromFS(templatesDir string, triggersDir string) (map[string]services.Notification, map[string][]triggers.Condition, error) {
   203  	templatesCfg := map[string]services.Notification{}
   204  	err := filepath.Walk(templatesDir, func(p string, info os.FileInfo, e error) error {
   205  		if e != nil {
   206  			return fmt.Errorf("error navigating the templates dirctory: %s : %w", templatesDir, e)
   207  		}
   208  		if info.IsDir() {
   209  			return nil
   210  		}
   211  		data, err := os.ReadFile(p)
   212  		if err != nil {
   213  			return fmt.Errorf("error reading the template file: %s : %w", p, err)
   214  		}
   215  		name := strings.Split(path.Base(p), ".")[0]
   216  		var template services.Notification
   217  		if err := yaml.Unmarshal(data, &template); err != nil {
   218  			return fmt.Errorf("error unmarshaling the data from file: %s : %w", p, err)
   219  		}
   220  		templatesCfg[name] = template
   221  		return nil
   222  	})
   223  	if err != nil {
   224  		return nil, nil, err
   225  	}
   226  
   227  	triggersCfg := map[string][]triggers.Condition{}
   228  	err = filepath.Walk(triggersDir, func(p string, info os.FileInfo, e error) error {
   229  		if e != nil {
   230  			return fmt.Errorf("error navigating the triggers dirctory: %s : %w", triggersDir, e)
   231  		}
   232  		if info.IsDir() {
   233  			return nil
   234  		}
   235  		data, err := os.ReadFile(p)
   236  		if err != nil {
   237  			return fmt.Errorf("error reading the trigger file: %s : %w", p, err)
   238  		}
   239  		name := strings.Split(path.Base(p), ".")[0]
   240  		var trigger []triggers.Condition
   241  		if err := yaml.Unmarshal(data, &trigger); err != nil {
   242  			return fmt.Errorf("error unmarshaling the data from file: %s : %w", p, err)
   243  		}
   244  		triggersCfg[name] = trigger
   245  		return nil
   246  	})
   247  	if err != nil {
   248  		return nil, nil, err
   249  	}
   250  	return templatesCfg, triggersCfg, nil
   251  }