repo_description: The Single Sign-On Multi-Factor portal for web apps, now OpenID Certified™
repo_topics: 2fa authentication docker golang kubernetes ldap mfa multifactor oauth2 openid-connect passkeys push-notifications security sso sso-authentication totp two-factor two-factor-authentication webauthn yubikey
code: package main
  
  import (
  	"encoding/json"
  	"fmt"
  	"os"
  	"os/exec"
  	"path/filepath"
  	"time"
  
  	"github.com/spf13/cobra"
  )
  
  func newADRCmd() *cobra.Command {
  	cmd := &cobra.Command{
  		Use:   "adr",
  		Short: "Generate an Architecture Decision Record",
  
  		DisableAutoGenTag: true,
  	}
  
  	cmd.AddCommand(newADRAddCmd())
  
  	return cmd
  }
  
  func newADRAddCmd() *cobra.Command {
  	cmd := &cobra.Command{
  		Use:   "add",
  		Short: "Add an Architecture Decision Record",
  		RunE:  adrAddRunE,
  
  		DisableAutoGenTag: true,
  	}
  
  	cmd.Flags().String("title", "", "sets the title of the record")
  	cmd.Flags().String("status", "", "sets the status of the record")
  	cmd.Flags().String("context", "", "sets the context of the record")
  	cmd.Flags().String("proposed-design", "", "sets the proposed design of the record")
  	cmd.Flags().String("decision", "", "sets the decision of the record")
  	cmd.Flags().String("consequences", "", "sets the consequences of the record")
  	cmd.Flags().IntSlice("related-adrs", nil, "sets the related adrs of the record")
  
  	return cmd
  }
  
  //nolint:gocyclo
  func adrAddRunE(cmd *cobra.Command, args []string) (err error) {
  	var adrs string
  
  	if adrs, err = getPFlagPath(cmd.Flags(), cmdFlagRoot, cmdFlagDocs, cmdFlagDocsContent, cmdFlagDocsADR); err != nil {
  		return err
  	}
  
  	c := filepath.Join(adrs, ".adr.config.json")
  
  	var raw []byte
  
  	if raw, err = os.ReadFile(c); err != nil {
  		return fmt.Errorf("error opening adr config: %w", err)
  	}
  
  	var config ArchitectureDesignRecordConfig
  
  	if err = json.Unmarshal(raw, &config); err != nil {
  		return fmt.Errorf("error parsing adr config: %w", err)
  	}
  
  	data := &ArchitectureDesignRecordTmpl{
  		ADR:       config.NextID,
  		Weight:    1000 + config.NextID,
  		Date:      time.Now().Format(dateFmtYAML),
  		DateISO:   time.Now().Format(time.DateOnly),
  		DateHuman: time.Now().Format("January 2, 2006"),
  	}
  
  	if data.Title, err = cmd.Flags().GetString("title"); err != nil {
  		return err
  	}
  
  	if data.Status, err = cmd.Flags().GetString("status"); err != nil {
  		return err
  	}
  
  	if data.Context, err = cmd.Flags().GetString("context"); err != nil {
  		return err
  	}
  
  	if data.ProposedDesign, err = cmd.Flags().GetString("proposed-design"); err != nil {
  		return err
  	}
  
  	if data.Decision, err = cmd.Flags().GetString("decision"); err != nil {
  		return err
  	}
  
  	if data.Consequences, err = cmd.Flags().GetString("consequences"); err != nil {
  		return err
  	}
  
  	if data.RelatedADRs, err = cmd.Flags().GetIntSlice("related-adrs"); err != nil {
  		return err
  	}
  
  	for _, related := range data.RelatedADRs {
  		if related >= config.NextID {
  			return fmt.Errorf("related adr %d does not exist yet", related)
  		}
  	}
  
  	fp := filepath.Join(adrs, fmt.Sprintf("%d.md", data.ADR))
  
  	var f *os.File
  
  	if f, err = os.Create(fp); err != nil {
  		return fmt.Errorf("error opening file for adr: %w", err)
  	}
  
  	defer f.Close()
  
  	if err = tmplADR.Execute(f, data); err != nil {
  		return fmt.Errorf("error executing template: %w", err)
  	}
  
  	config.NextID += 1
  
  	if raw, err = json.Marshal(config); err != nil {
  		return fmt.Errorf("error serializing config: %w", err)
  	}
  
  	if err = os.WriteFile(c, raw, 0600); err != nil {
  		return fmt.Errorf("error writing config: %w", err)
  	}
  
  	gitadd := exec.Command("git", "add", fp)
  
  	return gitadd.Run()
  }
  
  type ArchitectureDesignRecordConfig struct {
  	NextID int `json:"next_id"`
  }
  
  type ArchitectureDesignRecordTmpl struct {
  	ADR            int
  	Weight         int
  	Date           string
  	DateISO        string
  	DateHuman      string
  	Title          string
  	Status         string
  	Context        string
  	ProposedDesign string
  	Decision       string
  	Consequences   string
  	RelatedADRs    []int
  }
  