├── internal ├── cli │ ├── init │ │ ├── util.go │ │ └── template.go │ ├── config │ │ └── survey.go │ ├── cache │ │ ├── cachedFiles.go │ │ ├── CachedFile.go │ │ ├── new.go │ │ └── cache.go │ ├── setup │ │ ├── description.go │ │ └── check.go │ ├── cachedir │ │ ├── migrate.go │ │ ├── main.go │ │ └── projects.go │ ├── project │ │ ├── write.go │ │ ├── write_test.go │ │ └── create.go │ └── oak │ │ └── oak.go ├── http │ ├── requests.go │ ├── Header.go │ ├── general_test.go │ ├── Upload.go │ ├── progressReporter.go │ ├── general.go │ ├── Get.go │ └── Download.go ├── pom │ ├── parse.go │ └── artifact.go ├── aem │ ├── objects │ │ ├── projects.go │ │ ├── SystemInformation.go │ │ ├── crx.go │ │ ├── Description.go │ │ ├── instance.go │ │ └── config_test.go │ ├── Log.go │ ├── dispatcher │ │ ├── process.go │ │ ├── stop.go │ │ ├── invalidate.go │ │ └── start.go │ ├── indexes │ │ ├── structs.go │ │ └── indexes.go │ ├── generate │ │ ├── template.go │ │ ├── components.go │ │ └── template_test.go │ ├── Password.go │ ├── bundle │ │ └── objects.go │ ├── systeminformation │ │ └── SystemInformation.go │ ├── utility.go │ ├── start_test.go │ ├── http.go │ ├── cloudmanager │ │ └── git.go │ ├── stop.go │ ├── Find.go │ ├── config.go │ ├── utility_test.go │ ├── replication │ │ └── page.go │ ├── Find_test.go │ ├── Password_test.go │ └── http_test.go ├── manifest │ └── manifest.go ├── sliceutil │ ├── Inslice.go │ ├── Compare.go │ ├── Compare_test.go │ └── Inslice_test.go ├── commands │ ├── bash.go │ ├── zsh.go │ ├── version.go │ ├── index.go │ ├── shellCompletion.go │ ├── cdn.go │ ├── activation.go │ ├── bundle.go │ ├── oak.go │ ├── cloudmanager.go │ ├── projects.go │ ├── package.go │ ├── init.go │ ├── setupcheck.go │ ├── cloudmanagerPush.go │ ├── commands.go │ ├── packageRebuild.go │ ├── packageInstall.go │ ├── cloudmanagerGitAuthenticate.go │ ├── bundleList.go │ ├── indexes.go │ ├── build.go │ ├── stop.go │ ├── cdnCredentials.go │ ├── oakCheck.go │ ├── oakCompact.go │ ├── oakExplore.go │ ├── generatedump.go │ ├── oakCheckpoints.go │ ├── cloudUpgrade.go │ ├── bundleInstall.go │ ├── passwords.go │ ├── invalidate.go │ ├── activationTree.go │ ├── open.go │ ├── packageUpload.go │ ├── cdnPurgeService.go │ ├── oakConsole.go │ ├── replicationPage.go │ ├── bundleStop.go │ ├── bundleStart.go │ ├── cdnPurgeURL.go │ ├── packageList.go │ ├── cdnPurgeTag.go │ ├── log.go │ ├── start.go │ ├── pullContent.go │ ├── destroy.go │ ├── reindex.go │ └── packageDownload.go ├── version │ ├── version.go │ └── version_test.go ├── packageproperties │ ├── properties.go │ └── general.go ├── output │ └── output.go └── casetypes │ └── types.go ├── Dockerfile ├── excluding-functions.txt ├── main.go ├── scripts └── completions.sh ├── _templates ├── content-component │ └── settings.toml └── extend-core-components │ └── settings.toml ├── .travis.yml ├── Makefile ├── .gitignore ├── docker ├── assets │ └── setup.sh └── Makefile ├── install.sh ├── aem.init ├── go.mod ├── .goreleaser.yml └── CODE_OF_CONDUCT.md /internal/cli/init/util.go: -------------------------------------------------------------------------------- 1 | package init 2 | -------------------------------------------------------------------------------- /internal/http/requests.go: -------------------------------------------------------------------------------- 1 | package http 2 | -------------------------------------------------------------------------------- /internal/cli/config/survey.go: -------------------------------------------------------------------------------- 1 | package config 2 | -------------------------------------------------------------------------------- /internal/cli/init/template.go: -------------------------------------------------------------------------------- 1 | package init 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM httpd:2.4 2 | 3 | ADD docker/assets /assets 4 | RUN bash /assets/setup.sh 5 | 6 | -------------------------------------------------------------------------------- /internal/cli/cache/cachedFiles.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | type cachedFiles struct { 4 | File []CachedFile 5 | } 6 | -------------------------------------------------------------------------------- /excluding-functions.txt: -------------------------------------------------------------------------------- 1 | github.com/jlentink/aem/internal/output.Printf 2 | github.com/jlentink/aem/internal/output.Print 3 | -------------------------------------------------------------------------------- /internal/http/Header.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | // Header general http header struct 4 | type Header struct { 5 | Key string 6 | Value string 7 | } 8 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/commands" 5 | ) 6 | 7 | func main() { 8 | commands.Execute() 9 | } 10 | -------------------------------------------------------------------------------- /scripts/completions.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | rm -rf completions 4 | mkdir completions 5 | for sh in bash zsh; do 6 | go run main.go shell "$sh" >"completions/aem.$sh" 7 | done -------------------------------------------------------------------------------- /internal/pom/parse.go: -------------------------------------------------------------------------------- 1 | package pom 2 | 3 | // Open pom file 4 | func Open(path string) (*Pom, error) { 5 | pom := Pom{} 6 | err := pom.Open(path) 7 | 8 | return &pom, err 9 | } 10 | -------------------------------------------------------------------------------- /internal/cli/setup/description.go: -------------------------------------------------------------------------------- 1 | package setup 2 | 3 | // Description bin descriptor 4 | type Description struct { 5 | Bin string 6 | Description string 7 | Required int 8 | Found bool 9 | } 10 | -------------------------------------------------------------------------------- /internal/cli/cache/CachedFile.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import "time" 4 | 5 | // CachedFile Cached file 6 | type CachedFile struct { 7 | MD5 string 8 | URI []string 9 | Date time.Time 10 | OriginalName string 11 | } 12 | -------------------------------------------------------------------------------- /internal/aem/objects/projects.go: -------------------------------------------------------------------------------- 1 | package objects 2 | 3 | // Projects Registered projects 4 | type Projects struct { 5 | Project []ProjectRegistered 6 | } 7 | 8 | // ProjectRegistered registered project 9 | type ProjectRegistered struct { 10 | Name string 11 | Path string 12 | } 13 | -------------------------------------------------------------------------------- /internal/cli/cache/new.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | // New Cache 4 | func New() Cache { 5 | return NewWithRoot("") 6 | } 7 | 8 | // NewWithRoot cache root 9 | func NewWithRoot(root string) Cache { 10 | cache := Cache { 11 | root: root, 12 | } 13 | cache.createCacheDir() 14 | return cache 15 | } 16 | 17 | -------------------------------------------------------------------------------- /internal/aem/Log.go: -------------------------------------------------------------------------------- 1 | package aem 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/aem/objects" 5 | "github.com/jlentink/aem/internal/cli/project" 6 | "os" 7 | ) 8 | 9 | // ListLogFiles log files for instance 10 | func ListLogFiles(i *objects.Instance) ([]os.FileInfo, error) { 11 | logPath, _ := project.GetLogDirLocation(*i) 12 | files, err := project.ReadDir(logPath) 13 | if err != nil { 14 | return []os.FileInfo{}, err 15 | } 16 | return files, err 17 | } 18 | -------------------------------------------------------------------------------- /internal/manifest/manifest.go: -------------------------------------------------------------------------------- 1 | package manifest 2 | 3 | import "errors" 4 | 5 | // Manifest holds the manifest values 6 | type Manifest struct { 7 | keyValues map[string]string 8 | } 9 | 10 | // Get a value from the manifest based on the label 11 | func (m *Manifest) Get(manifestLabel string) (string, error) { 12 | if _, ok := m.keyValues[manifestLabel]; ok { 13 | return m.keyValues[manifestLabel], nil 14 | } 15 | 16 | return "", errors.New("could not find the key") 17 | } 18 | -------------------------------------------------------------------------------- /internal/cli/cachedir/migrate.go: -------------------------------------------------------------------------------- 1 | package cachedir 2 | 3 | import "github.com/jlentink/aem/internal/cli/project" 4 | 5 | func migrate(){ 6 | initiated = true 7 | if project.Exists(getCacheRoot()) && !project.IsFile(getCacheRoot()){ 8 | return 9 | } 10 | if project.Exists(getCacheRoot()) && project.IsFile(getCacheRoot()){ 11 | project.Rename(getCacheRoot(), getCacheRoot() +".bak") 12 | createCacheDir() 13 | project.Rename(getCacheRoot() +".bak",getCacheRoot() + "/projects.toml") 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /internal/sliceutil/Inslice.go: -------------------------------------------------------------------------------- 1 | package sliceutil 2 | 3 | // InSliceInt64 checks if slice contains int64 value. 4 | func InSliceInt64(slice []int64, needle int64) bool { 5 | for _, v := range slice { 6 | if v == needle { 7 | return true 8 | } 9 | } 10 | return false 11 | } 12 | 13 | // InSliceString checks if slice contains string value. 14 | func InSliceString(slice []string, needle string) bool { 15 | for _, v := range slice { 16 | if v == needle { 17 | return true 18 | } 19 | } 20 | return false 21 | } 22 | -------------------------------------------------------------------------------- /_templates/content-component/settings.toml: -------------------------------------------------------------------------------- 1 | name = "Simple Content component" 2 | description = "Simple component" 3 | destination = "ui.apps/src/main/content/jcr_root/apps/{{.project}}/components/content" 4 | version = "1.0.0" 5 | 6 | [[fields]] 7 | name = "id" 8 | question = "Component id" 9 | help = "What is the component id to use" 10 | type = "input" 11 | default = "C-01" 12 | 13 | [[fields]] 14 | name = "version" 15 | question = "What is the version for this component" 16 | help = "Versioning for components" 17 | type = "input" 18 | default = "v1" 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: go 3 | go: 4 | - 1.14.x 5 | env: 6 | global: 7 | - GO111MODULE=on 8 | - TRAVISBUILD=on 9 | install: 10 | - go get golang.org/x/lint/golint 11 | - go get github.com/fzipp/gocyclo 12 | - go get github.com/gordonklaus/ineffassign 13 | - go get github.com/danieljoos/wincred 14 | - go get github.com/konsorten/go-windows-terminal-sequences 15 | - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.24.0 16 | - go get -u github.com/gobuffalo/packr/v2/packr2 17 | 18 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all clean release testrelease snapshot packr lint 2 | 3 | all: snapshot 4 | 5 | clean: 6 | @-rm test-report.out 7 | @-rm coverage.out 8 | @-rm -rf build 9 | @-rm -rf dist 10 | @-rm -rf completions 11 | @-rm *.zip 12 | @-rm *.tbz2 13 | @-rm *.tgz 14 | @-rm aem 15 | @-rm ./dist 16 | 17 | release: clean lint 18 | goreleaser release --rm-dist 19 | 20 | testrelease: 21 | goreleaser --skip-publish --skip-validate --rm-dist 22 | 23 | snapshot: clean lint 24 | goreleaser --snapshot 25 | 26 | packr: 27 | packr2 28 | 29 | lint: 30 | golint -set_exit_status ./... 31 | -------------------------------------------------------------------------------- /internal/aem/dispatcher/process.go: -------------------------------------------------------------------------------- 1 | package dispatcher 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jlentink/aem/internal/aem/objects" 6 | "os/exec" 7 | "strings" 8 | ) 9 | 10 | // DaemonRunning Is the Docker deamon running? 11 | func DaemonRunning() bool { 12 | cmd := exec.Command("docker", "version") 13 | if err := cmd.Run() ; err != nil { 14 | if _, ok := err.(*exec.ExitError); ok { 15 | return true 16 | } 17 | return false 18 | } 19 | return true 20 | } 21 | 22 | func processName(cnf *objects.Config) string { 23 | return fmt.Sprintf("dispatcher-%s", strings.ToLower(cnf.ProjectName)) 24 | } -------------------------------------------------------------------------------- /internal/aem/dispatcher/stop.go: -------------------------------------------------------------------------------- 1 | package dispatcher 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jlentink/aem/internal/aem/objects" 6 | "os/exec" 7 | ) 8 | 9 | // Stop instance 10 | func Stop(i objects.Instance, cnf *objects.Config) error { 11 | if !DaemonRunning() { 12 | return fmt.Errorf("docker daemon is not running") 13 | } 14 | 15 | options := []string { 16 | "stop", 17 | processName(cnf), 18 | } 19 | cmd := exec.Command("docker", options...) 20 | cmd.Start() 21 | fmt.Printf("Waiting for dispatcher to stop...") 22 | err := cmd.Wait() 23 | fmt.Printf("Dispatcher stopped...") 24 | return err 25 | } -------------------------------------------------------------------------------- /internal/sliceutil/Compare.go: -------------------------------------------------------------------------------- 1 | package sliceutil 2 | 3 | import "strings" 4 | 5 | // StringCompare Compare string slices. 6 | func StringCompare(s1, s2 []string) bool { 7 | if len(s1) != len(s2) { 8 | return false 9 | } 10 | 11 | for i := range s1 { 12 | if s1[i] != s2[i] { 13 | return false 14 | } 15 | } 16 | return true 17 | } 18 | 19 | // StringInSliceEqualFold Check if string is in slice with equal fold 20 | func StringInSliceEqualFold(token string, stringSlice []string) bool{ 21 | for _, s := range stringSlice { 22 | if strings.EqualFold(token, s) { 23 | return true 24 | } 25 | } 26 | return false 27 | } 28 | 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /cmd/aemcli/instance 3 | .idea 4 | /cmd/aemcli/aemcli 5 | /cmd/aemcli/.scannerwork 6 | /cmd/aemcli/test-report.out 7 | /cmd/aemcli/coverage.out 8 | /cmd/aem/.scannerwork 9 | /cmd/aem/test-report.out 10 | /cmd/aem/sonar-project.properties 11 | /cmd/aem/coverage.out 12 | /cmd/aem/instance 13 | /cmd/aem/.aem 14 | /cmd/aem/aem 15 | /build 16 | /.scannerwork 17 | /*.zip 18 | /*.tbz2 19 | /*.tgz 20 | /aem 21 | /aem.toml 22 | /instances 23 | /test-report.out 24 | /templates 25 | /dist 26 | /completions 27 | /aem.example.toml 28 | /docker/assets/4.3.3 29 | /dispatcher 30 | /packrd 31 | /internal/commands/commands-packr.go 32 | /packrd/packed-packr.go 33 | /internal/aem/aem-packr.go 34 | -------------------------------------------------------------------------------- /internal/commands/bash.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/output" 5 | "github.com/spf13/cobra" 6 | "os" 7 | ) 8 | 9 | type commandBash struct { 10 | verbose bool 11 | } 12 | 13 | func (c *commandBash) setup() *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: "bash", 16 | Short: "Generate bash completion for aemCLI", 17 | PreRun: c.preRun, 18 | Run: c.run, 19 | } 20 | return cmd 21 | } 22 | 23 | func (c *commandBash) preRun(cmd *cobra.Command, args []string) { 24 | c.verbose, _ = cmd.Flags().GetBool("verbose") 25 | output.SetVerbose(verbose) 26 | } 27 | 28 | func (c *commandBash) run(cmd *cobra.Command, args []string) { 29 | rootCmd.GenBashCompletion(os.Stdout) // nolint: errcheck 30 | } 31 | -------------------------------------------------------------------------------- /internal/commands/zsh.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/output" 5 | "github.com/spf13/cobra" 6 | "os" 7 | ) 8 | 9 | type commandZsh struct { 10 | verbose bool 11 | } 12 | 13 | func (c *commandZsh) setup() *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: "zsh", 16 | Short: "Generate zsh completion for aemCLI", 17 | Aliases: []string{"zsh"}, 18 | PreRun: c.preRun, 19 | Run: c.run, 20 | } 21 | return cmd 22 | } 23 | 24 | func (c *commandZsh) preRun(cmd *cobra.Command, args []string) { 25 | c.verbose, _ = cmd.Flags().GetBool("verbose") 26 | output.SetVerbose(verbose) 27 | } 28 | 29 | func (c *commandZsh) run(cmd *cobra.Command, args []string) { 30 | rootCmd.GenZshCompletion(os.Stdout) // nolint: errcheck 31 | } 32 | -------------------------------------------------------------------------------- /internal/aem/indexes/structs.go: -------------------------------------------------------------------------------- 1 | package indexes 2 | 3 | const ( 4 | indexes = "/crx/server/crx.default/jcr:root/oak:index.1.json" 5 | reindexURL = "/oak:index/%s" 6 | ) 7 | 8 | // IndexList List of AEM indexes 9 | type IndexList struct { 10 | JcrPrimaryType string `json:":jcr:primaryType"` 11 | JcrMixinTypes []string `json:"jcr:mixinTypes"` 12 | } 13 | 14 | // Index AEM index 15 | type Index struct { 16 | Name string `json:"name"` 17 | Info string `json:"info"` 18 | Type string `json:"type"` 19 | QueryPaths []string `json:"queryPaths"` 20 | Async []string `json:"async"` 21 | ReindexCount int64 `json:"reindexCount"` 22 | IncludedPaths []string `json:"includedPaths"` 23 | ExcludedPaths []string `json:"excludedPaths"` 24 | } 25 | -------------------------------------------------------------------------------- /internal/version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import "fmt" 4 | 5 | var ( 6 | version = "snapshot" 7 | commit = "none" 8 | date = "unknown" 9 | builtBy = "unknown" 10 | ) 11 | 12 | // GetVersion Get version for application 13 | func GetVersion() string { 14 | return version 15 | } 16 | 17 | // GetBuild Get build hash for application 18 | func GetBuild() string { 19 | return commit 20 | } 21 | 22 | // DisplayVersion returns version string for application 23 | func DisplayVersion(v, m bool) string { 24 | if m { 25 | return fmt.Sprintf("%s\n", version) 26 | } else if v { 27 | return fmt.Sprintf("AEMcli (https://github.com/jlentink/aem)\n" + 28 | "Version: %s\n" + 29 | "Built: %s\n" + 30 | "Date: %s\n", version, commit, date) 31 | } 32 | return fmt.Sprintf("AEMcli\n" + 33 | "Version: %s\n", version) 34 | } 35 | -------------------------------------------------------------------------------- /internal/aem/generate/template.go: -------------------------------------------------------------------------------- 1 | package generate 2 | 3 | import ( 4 | "bytes" 5 | "github.com/jlentink/aem/internal/casetypes" 6 | "text/template" 7 | ) 8 | 9 | func templateFuncs() map[string]interface{} { 10 | return template.FuncMap{ 11 | "CC": casetypes.CamelCase, 12 | "UC": casetypes.UpperCase, 13 | "LC": casetypes.LowerCase, 14 | "PC": casetypes.PascalCase, 15 | "SC": casetypes.SnakeCase, 16 | "KC": casetypes.KababCase, 17 | "TC": casetypes.TitleCase, 18 | } 19 | } 20 | 21 | // ParseTemplate parses template string with map of variables 22 | func ParseTemplate(tpl string, vars map[string]string) (string, error) { 23 | t, err := template.New("toTemplate").Funcs(templateFuncs()).Parse(tpl) 24 | if err != nil { 25 | return "", err 26 | } 27 | 28 | var b bytes.Buffer 29 | err = t.Execute(&b, vars) 30 | if err != nil { 31 | return "", err 32 | } 33 | return b.String(), nil 34 | } 35 | -------------------------------------------------------------------------------- /internal/cli/project/write.go: -------------------------------------------------------------------------------- 1 | package project 2 | 3 | import ( 4 | "errors" 5 | "github.com/spf13/afero" 6 | "os" 7 | ) 8 | 9 | // WriteTextFile write textfile to disk 10 | func WriteTextFile(path, content string) (int, error) { 11 | bytes := 0 12 | f, err := Create(path) 13 | 14 | if err == nil { 15 | defer f.Close() // nolint: errcheck 16 | bytes, err = f.WriteString(content) 17 | } 18 | return bytes, err 19 | } 20 | 21 | // WriteGitIgnoreFile Write ignore file to disk 22 | func WriteGitIgnoreFile() (string, error) { 23 | path, err := getIgnoreFileLocation() 24 | if err != nil { 25 | return ``, err 26 | } 27 | if _, err := os.Stat(path); os.IsNotExist(err) { 28 | content := []byte(configInstanceGitIgnoreContent) 29 | err := afero.WriteFile(fs, path, content, 0644) 30 | if err != nil { 31 | return ``, errors.New("could not create ignore file") 32 | } 33 | } 34 | return path, nil 35 | } 36 | -------------------------------------------------------------------------------- /internal/commands/version.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/output" 5 | "github.com/jlentink/aem/internal/version" 6 | "github.com/spf13/cobra" 7 | "os" 8 | ) 9 | 10 | type commandVersion struct { 11 | verbose bool 12 | minimal bool 13 | } 14 | 15 | func (c *commandVersion) setup() *cobra.Command { 16 | cmd := &cobra.Command{ 17 | Use: "version", 18 | Short: "Show version of aemcli", 19 | PreRun: c.preRun, 20 | Run: c.run, 21 | } 22 | cmd.Flags().BoolVarP(&c.minimal, "minimal", "m", false, "Show the minimal version information") 23 | return cmd 24 | } 25 | 26 | func (c *commandVersion) preRun(cmd *cobra.Command, args []string) { 27 | c.verbose, _ = cmd.Flags().GetBool("verbose") 28 | } 29 | 30 | func (c *commandVersion) run(cmd *cobra.Command, args []string) { 31 | output.Print(output.NORMAL, version.DisplayVersion(c.verbose, c.minimal)) 32 | os.Exit(ExitNormal) 33 | } 34 | -------------------------------------------------------------------------------- /internal/commands/index.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/output" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | type commandIndex struct { 9 | verbose bool 10 | cmd *cobra.Command 11 | } 12 | 13 | func (c *commandIndex) setup() *cobra.Command { 14 | c.cmd = &cobra.Command{ 15 | Use: "indexes", 16 | Aliases: []string{}, 17 | Short: "index commands", 18 | PreRun: c.preRun, 19 | Run: c.run, 20 | } 21 | 22 | commands = []Command{ 23 | &commandIndexes{}, 24 | &commandReindex{}, 25 | } 26 | for _, cmd := range commands { 27 | c.cmd.AddCommand(cmd.setup()) 28 | } 29 | return c.cmd 30 | } 31 | 32 | func (c *commandIndex) preRun(cmd *cobra.Command, args []string) { 33 | c.verbose, _ = cmd.Flags().GetBool("verbose") 34 | output.SetVerbose(verbose) 35 | 36 | ConfigCheckListProjects() 37 | RegisterProject() 38 | } 39 | 40 | func (c *commandIndex) run(cmd *cobra.Command, args []string) { 41 | cmd.Help() // nolint: errcheck 42 | } 43 | -------------------------------------------------------------------------------- /_templates/extend-core-components/settings.toml: -------------------------------------------------------------------------------- 1 | name = "Extend a core component" 2 | description = "Component extending a Core component (https://github.com/adobe/aem-core-wcm-components)" 3 | destination = "ui.apps/src/main/content/jcr_root/apps/{{.project}}/components/content" 4 | version = "1.0.0" 5 | 6 | [[fields]] 7 | name = "id" 8 | question = "Component id" 9 | help = "What is the component id to use" 10 | type = "input" 11 | default = "C-01" 12 | 13 | [[fields]] 14 | name = "version" 15 | question = "What is the version for this component" 16 | help = "Versioning for components" 17 | type = "input" 18 | default = "v1" 19 | 20 | [[fields]] 21 | name = "corecomponent" 22 | question = "Which core component to extend" 23 | help = "More infomation at https://github.com/adobe/aem-core-wcm-components" 24 | type = "select" 25 | options = ["Text", "Title", "Image", "Download", "Embed", "Experience Fragment", "Teaser", "Button", "List", 26 | "Content Fragment", "Content Fragment List", "Separator", "Sharing"] 27 | default = "b" -------------------------------------------------------------------------------- /docker/assets/setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ln -s /usr/local/apache2/ /etc/httpd 4 | ln -s /usr/local/apache2/modules/ /etc/httpd/modules 5 | cp /assets/4.3.3/dispatcher-apache2.4-4.3.3.so /etc/httpd/modules/ 6 | cp /assets/4.3.3/dispatcher-apache2.4-4.3.3.so /etc/httpd/modules/ 7 | cp /assets/4.3.3/conf/mime.types /etc 8 | chmod 755 /etc/httpd/modules/dispatcher-apache2.4-4.3.3.so 9 | ln -s /usr/local/apache2/logs /etc/httpd/logs 10 | mkdir -p /etc/httpd/conf 11 | mv /etc/httpd/httpd.conf /etc/httpd/conf 12 | ln -s /etc/httpd/conf/httpd.conf /etc/httpd/httpd.conf 13 | mkdir -p /usr/local/apache2/conf.modules.d 14 | ln -s /usr/local/apache2/conf.modules.d /etc/httpd/conf.modules.d 15 | cp /assets/4.3.3/conf/modules.d/* /usr/local/apache2/conf.modules.d 16 | ln -s /usr/local/apache2/modules/dispatcher-apache2.4-4.3.3.so /usr/local/apache2/modules/mod_dispatcher.so 17 | addgroup apache 18 | useradd -g apache apache 19 | mkdir -p /var/www/html \ 20 | /var/www/author \ 21 | /mnt/var/www/default \ 22 | /var/www/author \ 23 | /var/www/lc 24 | -------------------------------------------------------------------------------- /internal/commands/shellCompletion.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/output" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | type commandShellCompletion struct { 9 | verbose bool 10 | cmd *cobra.Command 11 | } 12 | 13 | func (c *commandShellCompletion) setup() *cobra.Command { 14 | c.cmd = &cobra.Command{ 15 | Use: "shell", 16 | Aliases: []string{}, 17 | Short: "Shell completion commands", 18 | PreRun: c.preRun, 19 | Run: c.run, 20 | } 21 | 22 | commands = []Command{ 23 | &commandZsh{}, 24 | &commandBash{}, 25 | } 26 | for _, cmd := range commands { 27 | c.cmd.AddCommand(cmd.setup()) 28 | } 29 | return c.cmd 30 | } 31 | 32 | func (c *commandShellCompletion) preRun(cmd *cobra.Command, args []string) { 33 | c.verbose, _ = cmd.Flags().GetBool("verbose") 34 | output.SetVerbose(verbose) 35 | 36 | ConfigCheckListProjects() 37 | RegisterProject() 38 | } 39 | 40 | func (c *commandShellCompletion) run(cmd *cobra.Command, args []string) { 41 | cmd.Help() // nolint: errcheck 42 | } 43 | -------------------------------------------------------------------------------- /internal/commands/cdn.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/output" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | type commandCdn struct { 9 | verbose bool 10 | cmd *cobra.Command 11 | } 12 | 13 | func (c *commandCdn) setup() *cobra.Command { 14 | c.cmd = &cobra.Command{ 15 | Use: "cdn", 16 | Aliases: []string{}, 17 | Short: "cdn commands", 18 | PreRun: c.preRun, 19 | Run: c.run, 20 | } 21 | 22 | commands = []Command{ 23 | &commandCdnPurgeURL{}, 24 | &commandCdnPurgeService{}, 25 | &commandCdnPurgeTag{}, 26 | &commandCdnCredentials{}, 27 | } 28 | for _, cmd := range commands { 29 | c.cmd.AddCommand(cmd.setup()) 30 | } 31 | return c.cmd 32 | } 33 | 34 | func (c *commandCdn) preRun(cmd *cobra.Command, args []string) { 35 | c.verbose, _ = cmd.Flags().GetBool("verbose") 36 | output.SetVerbose(verbose) 37 | 38 | ConfigCheckListProjects() 39 | RegisterProject() 40 | } 41 | 42 | func (c *commandCdn) run(cmd *cobra.Command, args []string) { 43 | cmd.Help() // nolint: errcheck 44 | } 45 | -------------------------------------------------------------------------------- /internal/commands/activation.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/output" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | type commandActivation struct { 9 | verbose bool 10 | cmd *cobra.Command 11 | } 12 | 13 | func (c *commandActivation) setup() *cobra.Command { 14 | c.cmd = &cobra.Command{ 15 | Use: "activation", 16 | Aliases: []string{}, 17 | Short: "Activation commands", 18 | PreRun: c.preRun, 19 | Run: c.run, 20 | } 21 | 22 | commands = []Command{ 23 | &commandReplicationPage{}, 24 | &commandActivateTree{}, 25 | &commandInvalidate{}, 26 | } 27 | for _, cmd := range commands { 28 | c.cmd.AddCommand(cmd.setup()) 29 | } 30 | return c.cmd 31 | } 32 | 33 | func (c *commandActivation) preRun(cmd *cobra.Command, args []string) { 34 | c.verbose, _ = cmd.Flags().GetBool("verbose") 35 | output.SetVerbose(verbose) 36 | 37 | ConfigCheckListProjects() 38 | RegisterProject() 39 | } 40 | 41 | func (c *commandActivation) run(cmd *cobra.Command, args []string) { 42 | c.cmd.Help() // nolint: errcheck 43 | } 44 | -------------------------------------------------------------------------------- /internal/commands/bundle.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/output" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | type commandBundle struct { 9 | verbose bool 10 | cmd *cobra.Command 11 | } 12 | 13 | func (c *commandBundle) setup() *cobra.Command { 14 | c.cmd = &cobra.Command{ 15 | Use: "bundle", 16 | Aliases: []string{}, 17 | Short: "Bundle commands", 18 | PreRun: c.preRun, 19 | Run: c.run, 20 | } 21 | 22 | commands = []Command{ 23 | &commandBundleList{}, 24 | &commandBundleInstall{}, 25 | &commandBundleStart{}, 26 | &commandBundelStop{}, 27 | } 28 | for _, cmd := range commands { 29 | c.cmd.AddCommand(cmd.setup()) 30 | } 31 | return c.cmd 32 | } 33 | 34 | func (c *commandBundle) preRun(cmd *cobra.Command, args []string) { 35 | c.verbose, _ = cmd.Flags().GetBool("verbose") 36 | output.SetVerbose(verbose) 37 | 38 | ConfigCheckListProjects() 39 | RegisterProject() 40 | } 41 | 42 | func (c *commandBundle) run(cmd *cobra.Command, args []string) { 43 | cmd.Help() // nolint: errcheck 44 | } 45 | -------------------------------------------------------------------------------- /internal/commands/oak.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/output" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | type commandOak struct { 9 | verbose bool 10 | cmd *cobra.Command 11 | } 12 | 13 | func (c *commandOak) setup() *cobra.Command { 14 | c.cmd = &cobra.Command{ 15 | Use: "oak", 16 | Aliases: []string{}, 17 | Short: "Oak commands", 18 | PreRun: c.preRun, 19 | Run: c.run, 20 | } 21 | 22 | commands = []Command{ 23 | &commandOakCheck{}, 24 | &commandOakCheckpoints{}, 25 | &commandOakCompact{}, 26 | &commandOakConsole{}, 27 | &commandOakExplore{}, 28 | } 29 | for _, cmd := range commands { 30 | c.cmd.AddCommand(cmd.setup()) 31 | } 32 | return c.cmd 33 | } 34 | 35 | func (c *commandOak) preRun(cmd *cobra.Command, args []string) { 36 | c.verbose, _ = cmd.Flags().GetBool("verbose") 37 | output.SetVerbose(verbose) 38 | 39 | ConfigCheckListProjects() 40 | RegisterProject() 41 | } 42 | 43 | func (c *commandOak) run(cmd *cobra.Command, args []string) { 44 | cmd.Help() // nolint: errcheck 45 | } 46 | -------------------------------------------------------------------------------- /internal/commands/cloudmanager.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/output" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | type commandCloudManager struct { 9 | verbose bool 10 | cmd *cobra.Command 11 | } 12 | 13 | func (c *commandCloudManager) setup() *cobra.Command { 14 | c.cmd = &cobra.Command{ 15 | Use: "cloudmanager", 16 | Aliases: []string{"cm"}, 17 | Short: "Adobe Cloud manager commands", 18 | PreRun: c.preRun, 19 | Run: c.run, 20 | } 21 | 22 | commands = []Command{ 23 | &commandCloudManagerPush{}, 24 | &commandCloudManagerGitAuthenticate{}, 25 | } 26 | for _, cmd := range commands { 27 | c.cmd.AddCommand(cmd.setup()) 28 | } 29 | return c.cmd 30 | } 31 | 32 | func (c *commandCloudManager) preRun(cmd *cobra.Command, args []string) { 33 | c.verbose, _ = cmd.Flags().GetBool("verbose") 34 | output.SetVerbose(verbose) 35 | 36 | ConfigCheckListProjects() 37 | RegisterProject() 38 | } 39 | 40 | func (c *commandCloudManager) run(cmd *cobra.Command, args []string) { 41 | cmd.Help() // nolint: errcheck 42 | } 43 | -------------------------------------------------------------------------------- /internal/http/general_test.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "net/url" 5 | "testing" 6 | ) 7 | 8 | func Test_URLToUrlString(t *testing.T) { 9 | type args struct { 10 | u *url.URL 11 | s string 12 | } 13 | tests := []struct { 14 | name string 15 | args args 16 | want string 17 | }{ 18 | { 19 | name: "plain", 20 | args: args{s: "http://www.google.com"}, 21 | want: "http://www.google.com/", 22 | }, 23 | { 24 | name: "auth", 25 | args: args{s: "http://1:2@www.google.com"}, 26 | want: "http://www.google.com/", 27 | }, 28 | { 29 | name: "query", 30 | args: args{s: "http://www.google.com?a=b"}, 31 | want: "http://www.google.com/?a=b", 32 | }, 33 | { 34 | name: "path", 35 | args: args{s: "http://www.google.com/a/b.html"}, 36 | want: "http://www.google.com/a/b.html", 37 | }, 38 | } 39 | for _, tt := range tests { 40 | t.Run(tt.name, func(t *testing.T) { 41 | tt.args.u, _ = url.Parse(tt.args.s) 42 | if got := URLToURLString(tt.args.u); got != tt.want { 43 | t.Errorf("urlToUrlString() = %v, want %v", got, tt.want) 44 | } 45 | }) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /internal/aem/Password.go: -------------------------------------------------------------------------------- 1 | package aem 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jlentink/aem/internal/aem/objects" 6 | "github.com/zalando/go-keyring" 7 | ) 8 | 9 | const ( 10 | serviceNameID = "aem-cli" 11 | ) 12 | 13 | func serviceName(i objects.Instance) string { 14 | return serviceNameID + "-" + i.Name + "-" + i.Hostname 15 | } 16 | 17 | // KeyRingSetPassword Store password in keyring. 18 | func KeyRingSetPassword(i objects.Instance, password string) error { 19 | return keyring.Set(serviceName(i), i.Username, password) 20 | } 21 | 22 | // KeyRingGetPassword Store password from keyring. 23 | func KeyRingGetPassword(i objects.Instance) (string, error) { 24 | return keyring.Get(serviceName(i), i.Username) 25 | } 26 | 27 | // GetPasswordForInstance get password for instance. 28 | func GetPasswordForInstance(i objects.Instance, useKeyring bool) (string, error) { 29 | if useKeyring { 30 | pw, err := keyring.Get(serviceName(i), i.Username) 31 | if err != nil { 32 | return ``, fmt.Errorf("could not get password from keychain for %s", i.Name) 33 | } 34 | return pw, nil 35 | } 36 | return i.Password, nil 37 | } 38 | -------------------------------------------------------------------------------- /internal/commands/projects.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jlentink/aem/internal/cli/cachedir" 6 | "github.com/jlentink/aem/internal/output" 7 | "github.com/spf13/cobra" 8 | "os" 9 | ) 10 | 11 | type commandProjects struct { 12 | verbose bool 13 | } 14 | 15 | func (c *commandProjects) setup() *cobra.Command { 16 | cmd := &cobra.Command{ 17 | Use: "projects", 18 | Short: "List know projects", 19 | PreRun: c.preRun, 20 | Run: c.run, 21 | } 22 | return cmd 23 | } 24 | 25 | func (c *commandProjects) preRun(cmd *cobra.Command, args []string) { 26 | c.verbose, _ = cmd.Flags().GetBool("verbose") 27 | output.SetVerbose(verbose) 28 | RegisterProject() 29 | } 30 | 31 | func (c *commandProjects) run(cmd *cobra.Command, args []string) { 32 | projects := cachedir.RegisteredProjects() 33 | projects = cachedir.ProjectsSort(projects) 34 | for _, project := range projects { 35 | fmt.Printf(" * %s - %s\n", project.Name, project.Path) 36 | } 37 | if len(projects) == 0 { 38 | output.Printf(output.NORMAL, "\U00002049 No registered projects found.") 39 | os.Exit(ExitError) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /internal/cli/cache/cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jlentink/aem/internal/cli/project" 6 | "github.com/spf13/afero" 7 | ) 8 | 9 | const cacheDir = ".aem" 10 | const filesDir = "files" 11 | const filesCacheFile = "files.toml" 12 | 13 | // Cache system 14 | type Cache struct { 15 | fs afero.Fs 16 | root string 17 | } 18 | 19 | func (c *Cache) getCacheRoot() (string, error) { 20 | homedir, err := project.HomeDir() 21 | if err != nil { 22 | return ``, err 23 | } 24 | c.root = fmt.Sprintf("%s/%s", homedir, cacheDir) 25 | return c.root, nil 26 | } 27 | 28 | func (c *Cache) createFs() { 29 | if c.root == "" { 30 | c.getCacheRoot() 31 | } 32 | c.fs = afero.NewBasePathFs(afero.NewOsFs(), c.root) 33 | } 34 | 35 | func (c *Cache) createCacheDir() error { 36 | root, err := c.getCacheRoot() 37 | if err != nil { 38 | return err 39 | } 40 | 41 | exists, err := afero.DirExists(c.fs, root + "/" + filesDir) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | if !exists { 47 | err := c.fs.MkdirAll(root + "/" + filesDir, 0755) 48 | if err != nil { 49 | return err 50 | } 51 | } 52 | return nil 53 | } -------------------------------------------------------------------------------- /internal/commands/package.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/output" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | type commandPackage struct { 9 | verbose bool 10 | cmd *cobra.Command 11 | } 12 | 13 | func (c *commandPackage) setup() *cobra.Command { 14 | c.cmd = &cobra.Command{ 15 | Use: "package", 16 | Aliases: []string{}, 17 | Short: "Package commands", 18 | PreRun: c.preRun, 19 | Run: c.run, 20 | } 21 | 22 | commands = []Command{ 23 | &commandPackageDownload{}, 24 | &commandPackageCopy{}, 25 | &commandPackageInstall{}, 26 | &commandPackageUpload{}, 27 | &commandPackageRebuild{}, 28 | &commandPackageList{}, 29 | &commandPackageDelete{}, 30 | } 31 | for _, cmd := range commands { 32 | c.cmd.AddCommand(cmd.setup()) 33 | } 34 | return c.cmd 35 | } 36 | 37 | func (c *commandPackage) preRun(cmd *cobra.Command, args []string) { 38 | c.verbose, _ = cmd.Flags().GetBool("verbose") 39 | output.SetVerbose(verbose) 40 | 41 | ConfigCheckListProjects() 42 | RegisterProject() 43 | } 44 | 45 | func (c *commandPackage) run(cmd *cobra.Command, args []string) { 46 | cmd.Help() // nolint: errcheck 47 | } 48 | -------------------------------------------------------------------------------- /internal/cli/cachedir/main.go: -------------------------------------------------------------------------------- 1 | package cachedir 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jlentink/aem/internal/cli/project" 6 | "github.com/jlentink/aem/internal/output" 7 | ) 8 | 9 | var doCache = true 10 | const cacheDir = ".aem" 11 | const filesDir = "files" 12 | var initiated = false 13 | 14 | // Init Cache 15 | func Init() { 16 | migrate() 17 | createCacheDir() 18 | } 19 | 20 | // Disable cache 21 | func Disable(){ 22 | doCache = false 23 | } 24 | 25 | func createCacheDir() { 26 | if !project.Exists(getCacheRoot()) { 27 | _, err := project.CreateDir(getCacheRoot()) 28 | if err != nil { 29 | output.Printf(output.VERBOSE, "Could not create cacheDir %s", err.Error()) 30 | } 31 | } 32 | if !project.Exists(getCacheRoot() + "/" + filesDir) { 33 | _, err := project.CreateDir(getCacheRoot() + "/" + filesDir) 34 | if err != nil { 35 | output.Printf(output.VERBOSE, "Could not create cacheDir %s", err.Error()) 36 | } 37 | } 38 | } 39 | 40 | func getCacheRoot() string { 41 | homedir, err := project.HomeDir() 42 | if err != nil { 43 | output.Printf(output.VERBOSE, "Could not find homedir %s", err.Error()) 44 | return `` 45 | } 46 | return fmt.Sprintf("%s/%s", homedir, cacheDir) 47 | } 48 | -------------------------------------------------------------------------------- /internal/sliceutil/Compare_test.go: -------------------------------------------------------------------------------- 1 | package sliceutil 2 | 3 | import "testing" 4 | 5 | func TestSliceStringCompare(t *testing.T) { 6 | type args struct { 7 | s1 []string 8 | s2 []string 9 | } 10 | tests := []struct { 11 | name string 12 | args args 13 | want bool 14 | }{ 15 | { 16 | name: "Equal slices", 17 | args: args{ 18 | s1: []string{"1", "2"}, 19 | s2: []string{"1", "2"}, 20 | }, 21 | want: true, 22 | }, 23 | { 24 | name: "different length long 1", 25 | args: args{ 26 | s1: []string{"1", "2"}, 27 | s2: []string{"1", "2", "3"}, 28 | }, 29 | want: false, 30 | }, 31 | { 32 | name: "different length long 2", 33 | args: args{ 34 | s1: []string{"1", "2", "3"}, 35 | s2: []string{"1", "2"}, 36 | }, 37 | want: false, 38 | }, 39 | { 40 | name: "Different value", 41 | args: args{ 42 | s1: []string{"1", "2", "3"}, 43 | s2: []string{"1", "2", "4"}, 44 | }, 45 | want: false, 46 | }, 47 | } 48 | for _, tt := range tests { 49 | t.Run(tt.name, func(t *testing.T) { 50 | if got := StringCompare(tt.args.s1, tt.args.s2); got != tt.want { 51 | t.Errorf("StringCompare() = %v, want %v", got, tt.want) 52 | } 53 | }) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | BINDIR=${BINDIR:-~/bin} 4 | VERSION=${VERSION:-1.0.1} 5 | 6 | while getopts "b:v:" opt; do 7 | case ${opt} in 8 | b ) 9 | BINDIR="${OPTARG}" 10 | ;; 11 | v ) 12 | VERSION="${OPTARG}" 13 | ;; 14 | \? ) echo "Usage: cmd [-h] [-t]" 15 | ;; 16 | esac 17 | done 18 | 19 | if [ -d "${BINDIR}" ]; then 20 | mkdir -p ${BINDIR} 21 | fi 22 | 23 | if [ "$(uname -s)" == "Darwin" ]; then 24 | echo "Downloading OSX version..." 25 | cd /tmp 26 | curl -sL https://github.com/jlentink/aem/releases/download/${VERSION}/aem_Darwin_x86_64.tar.gz --output /tmp/osx-${VERSION}.zip 27 | unzip -o /tmp/osx-${VERSION}.zip 28 | echo "Placing aemCLI bineary in ${BINDIR}" 29 | mv -f /tmp/aem ${BINDIR} 30 | else 31 | echo "Downloading Linux version..." 32 | cd /tmp 33 | mkdir -p ${BINDIR} 34 | curl -sL https://github.com/jlentink/aem/releases/download/${VERSION}/aem_Linux_x86_64.tar.gz --output /tmp/linux-v${VERSION}.tgz 35 | tar -zxf /tmp/linux-v${VERSION}.tgz 36 | echo "Placing aemCLI bineary in ${BINDIR}" 37 | mv -f /tmp/aem ${BINDIR} 38 | fi 39 | 40 | echo "execute \"aem\" from a project folder" 41 | echo "if aem could not be found add ${BINDIR} to your path" 42 | -------------------------------------------------------------------------------- /internal/aem/bundle/objects.go: -------------------------------------------------------------------------------- 1 | package bundle 2 | 3 | const ( 4 | bundlesURL = "/system/console/bundles" 5 | bundlePageURL = "/system/console/bundles/%s" 6 | ) 7 | 8 | // BundleRawState Bundles states from integer to string map 9 | var BundleRawState = map[int]string{ 10 | 0: "Unknown", 11 | 1: "Uninstalled", 12 | 2: "Installed", 13 | 4: "Resolved", 14 | 8: "Starting", 15 | 16: "Stopping", 16 | 32: "Active", 17 | } 18 | 19 | const ( 20 | bundleFormActionField = "action" 21 | bundleInstall = "install" 22 | bundleRefresh = "refreshPackages" 23 | ) 24 | 25 | type bundlesFeed struct { 26 | Status string `json:"status"` 27 | S []int `json:"s"` 28 | Data []Bundle `json:"data"` 29 | } 30 | 31 | //Bundle OSGI bundle struct 32 | type Bundle struct { 33 | ID int `json:"id"` 34 | Name string `json:"name"` 35 | Fragment bool `json:"fragment"` 36 | StateRaw int `json:"stateRaw"` 37 | State string `json:"state"` 38 | Version string `json:"version"` 39 | SymbolicName string `json:"symbolicName"` 40 | Category string `json:"category"` 41 | } 42 | 43 | type bundleResponse struct { 44 | Fragment bool `json:"fragment"` 45 | StateRaw int `json:"stateRaw"` 46 | } 47 | -------------------------------------------------------------------------------- /internal/pom/artifact.go: -------------------------------------------------------------------------------- 1 | package pom 2 | 3 | import "fmt" 4 | 5 | // Artifact types 6 | const ( 7 | Unknown = iota 8 | Bundle 9 | Package 10 | All 11 | ) 12 | 13 | // Artifact holding struct 14 | type Artifact struct { 15 | Name string 16 | BasePath string 17 | Path string 18 | ID string 19 | Version string 20 | Packaging string 21 | Parent string 22 | } 23 | 24 | // CompletePath complete path to artifact 25 | func (a *Artifact) CompletePath() string { 26 | return fmt.Sprintf("%s%s", a.Path, a.Filename()) 27 | } 28 | 29 | // Kind What is the kind of the artifact 30 | func (a *Artifact) Kind() int { 31 | switch a.Packaging { 32 | case "content-package": 33 | return Package 34 | case "bundle": 35 | return Bundle 36 | default: 37 | return Unknown 38 | } 39 | } 40 | 41 | // Filename get the filename for the artifact 42 | func (a *Artifact) Filename() string { 43 | switch a.Kind() { 44 | case Package: 45 | return fmt.Sprintf("%s-%s.zip", a.ID, a.Version) 46 | case Bundle: 47 | return fmt.Sprintf("%s-%s.jar", a.ID, a.Version) 48 | default: 49 | return fmt.Sprintf("%s-%s.unkown", a.ID, a.Version) 50 | } 51 | } 52 | 53 | // PakageName returns the package name 54 | func (a *Artifact) PakageName() string { 55 | return a.ID 56 | } 57 | -------------------------------------------------------------------------------- /internal/aem/systeminformation/SystemInformation.go: -------------------------------------------------------------------------------- 1 | package systeminformation 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/jlentink/aem/internal/aem" 6 | "github.com/jlentink/aem/internal/aem/objects" 7 | ) 8 | 9 | const ( 10 | // URLSystemInformation System information url 11 | URLSystemInformation = "/libs/granite/operations/content/systemoverview/export.json" 12 | ) 13 | 14 | // GetSystemInformation gets system information from running instance 15 | func GetSystemInformation(i *objects.Instance) (*objects.SystemInformation, error) { 16 | body, err := aem.GetFromInstance(i, URLSystemInformation) 17 | if err != nil { 18 | return nil, err 19 | } 20 | sysInfo := objects.SystemInformation{} 21 | err = json.Unmarshal(body, &sysInfo) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | if len(sysInfo.SystemInformation.Windows) > 0 { 27 | sysInfo.SystemInformation.CurrentOS = "Windows " + sysInfo.SystemInformation.Windows 28 | } else if len(sysInfo.SystemInformation.Linux) > 0 { 29 | sysInfo.SystemInformation.CurrentOS = "Linux " + sysInfo.SystemInformation.Linux 30 | } else if len(sysInfo.SystemInformation.MacOSX) > 0 { 31 | sysInfo.SystemInformation.CurrentOS = "MacOS " + sysInfo.SystemInformation.MacOSX 32 | } 33 | 34 | return &sysInfo, nil 35 | } 36 | -------------------------------------------------------------------------------- /aem.init: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ### BEGIN INIT INFO 3 | # Provides: aem 4 | # Required-Start: $remote_fs $syslog 5 | # Required-Stop: $remote_fs $syslog 6 | # Default-Start: 2 3 4 5 7 | # Default-Stop: 0 1 6 8 | # Short-Description: AEM wrapper 9 | # Description: Enable service to start stop aem. 10 | ### END INIT INFO 11 | 12 | dir="/var/aem/" 13 | cmd="aem" 14 | user="aem" 15 | 16 | name=`basename $0` 17 | stdout_log="/var/log/$name.log" 18 | stderr_log="/var/log/$name.err" 19 | 20 | 21 | case "$1" in 22 | start) 23 | echo "Starting $name" 24 | cd "$dir" 25 | if [ -z "$user" ]; then 26 | sudo $cmd start >> "$stdout_log" 2>> "$stderr_log" & 27 | else 28 | sudo -u "$user" $cmd start >> "$stdout_log" 2>> "$stderr_log" & 29 | fi 30 | ;; 31 | stop) 32 | echo "Stoping $name" 33 | cd "$dir" 34 | if [ -z "$user" ]; then 35 | sudo $cmd stop >> "$stdout_log" 2>> "$stderr_log" & 36 | else 37 | sudo -u "$user" $cmd start >> "$stdout_log" 2>> "$stderr_log" & 38 | fi 39 | ;; 40 | restart) 41 | echo "unsuported" 42 | ;; 43 | status) 44 | echo "unsuported" 45 | ;; 46 | *) 47 | echo "Usage: $0 {start|stop|restart|status}" 48 | exit 1 49 | ;; 50 | esac 51 | 52 | exit 0 53 | -------------------------------------------------------------------------------- /internal/packageproperties/properties.go: -------------------------------------------------------------------------------- 1 | package packageproperties 2 | 3 | import ( 4 | "encoding/xml" 5 | "fmt" 6 | ) 7 | 8 | // Label descriptors 9 | const ( 10 | CreatedBy = "createdBy" 11 | AllowIndexDefinitions = "allowIndexDefinitions" 12 | Name = "name" 13 | GroupID = "groupId" 14 | Version = "version" 15 | PackageType = "packageType" 16 | RequiresRoot = "requiresRoot" 17 | Group = "group" 18 | Description = "description" 19 | ArtifactID = "artifactId" 20 | ) 21 | 22 | type propertiesXML struct { 23 | XMLName xml.Name `xml:"properties"` 24 | Text string `xml:",chardata"` 25 | Comment struct { 26 | Text string `xml:",chardata"` 27 | } `xml:"comment"` 28 | Entry []struct { 29 | Text string `xml:",chardata"` 30 | Key string `xml:"key,attr"` 31 | } `xml:"entry"` 32 | } 33 | 34 | // Properties description struct 35 | type Properties struct { 36 | Comment string 37 | Property map[string]string 38 | } 39 | 40 | // Get gets the value from the package properties 41 | func (p *Properties) Get(key string) (string, error) { 42 | if _, ok := p.Property[key]; ok { 43 | return p.Property[key], nil 44 | } 45 | return "", fmt.Errorf("could not find key: %s", key) 46 | } 47 | -------------------------------------------------------------------------------- /internal/aem/utility.go: -------------------------------------------------------------------------------- 1 | package aem 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jlentink/aem/internal/aem/objects" 6 | "github.com/jlentink/aem/internal/cli/project" 7 | "github.com/jlentink/aem/internal/output" 8 | "os/user" 9 | "regexp" 10 | ) 11 | 12 | var ( 13 | u *user.User 14 | ) 15 | 16 | // WriteLicense Write license file to disk for instance 17 | func WriteLicense(i *objects.Instance, c *objects.Config) (int, error) { 18 | licensePath, _ := project.GetLicenseLocation(*i) 19 | if !project.Exists(licensePath) && len(c.LicenseCustomer) > 0 { 20 | output.Printf(output.VERBOSE, "No license in place found one in config writing\n") 21 | license := fmt.Sprintf( 22 | "# Adobe Granite License Properties\n"+ 23 | "license.product.name=Adobe Experience Manager\n"+ 24 | "license.customer.name=%s\n"+ 25 | "license.product.version=%s\n"+ 26 | "license.downloadID=%s\n", c.LicenseCustomer, c.LicenseVersion, c.LicenseDownloadID) 27 | return project.WriteTextFile(licensePath, license) 28 | } 29 | return 0, nil 30 | } 31 | 32 | func getCurrentUser() (*user.User, error) { 33 | if u != nil { 34 | return u, nil 35 | } 36 | 37 | return user.Current() 38 | } 39 | func isURL(s string) bool { 40 | if len(s) < 7 { 41 | return false 42 | } 43 | 44 | r, _ := regexp.Compile(`(?i)^http(s?)://`) 45 | return r.MatchString(s) 46 | } 47 | -------------------------------------------------------------------------------- /internal/http/Upload.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "bytes" 5 | "github.com/go-http-utils/headers" 6 | "github.com/jlentink/aem/internal/version" 7 | "net/http" 8 | "net/url" 9 | ) 10 | 11 | // UploadWithURL upload bytes.buffer with *url.URL 12 | func UploadWithURL(uri *url.URL, body *bytes.Buffer, httpHeaders []Header) (*http.Response, error) { 13 | pr := &progressReporter{ 14 | r: body, 15 | totalSize: uint64(body.Len()), 16 | label: "Uploading", 17 | } 18 | 19 | req, _ := http.NewRequest(http.MethodPost, ``, pr) 20 | req.URL = uri 21 | 22 | for _, header := range httpHeaders { 23 | req.Header.Add(header.Key, header.Value) 24 | } 25 | 26 | req.Header.Add(headers.UserAgent, "aemCLI - "+version.GetVersion()) 27 | if p, ps := uri.User.Password(); ps { 28 | req.SetBasicAuth(uri.User.Username(), p) 29 | } 30 | 31 | client := &http.Client{} 32 | return client.Do(req) 33 | } 34 | 35 | // Upload bytes.buffer with string username, password and uri 36 | func Upload(uri, username, password string, body *bytes.Buffer, httpHeaders []Header) (*http.Response, error) { 37 | URL, err := url.Parse(uri) 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | if username != "" || password != "" { 43 | URL.User = url.UserPassword(username, password) 44 | } 45 | 46 | return UploadWithURL(URL, body, httpHeaders) 47 | } 48 | -------------------------------------------------------------------------------- /internal/commands/init.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/aem" 5 | "github.com/jlentink/aem/internal/output" 6 | "github.com/spf13/cobra" 7 | "os" 8 | ) 9 | 10 | type commandInit struct { 11 | verbose bool 12 | force bool 13 | } 14 | 15 | func (c *commandInit) setup() *cobra.Command { 16 | cmd := &cobra.Command{ 17 | Use: "init", 18 | Long: "Init new project in current directory by writing aem.toml. Edit it before starting you project. Run this file on the same level as the root pom.xml", 19 | Short: "Init new project", 20 | PreRun: c.preRun, 21 | Run: c.run, 22 | } 23 | cmd.Flags().BoolVarP(&c.force, "force", "f", false, "Force override of current configuration") 24 | return cmd 25 | } 26 | 27 | func (c *commandInit) preRun(cmd *cobra.Command, args []string) { 28 | c.verbose, _ = cmd.Flags().GetBool("verbose") 29 | output.SetVerbose(c.verbose) 30 | } 31 | 32 | func (c *commandInit) run(cmd *cobra.Command, args []string) { 33 | output.Printf(output.NORMAL, "Writing sample file...\n") 34 | _, err := aem.WriteConfigFile() 35 | if err != nil { 36 | output.Printf(output.NORMAL, "Could not write config file. (%s)", err.Error()) 37 | os.Exit(ExitError) 38 | } 39 | output.Printf(output.NORMAL, "Please copy the \"aem.example.toml\" if new or compare with your existing \"aem.toml\" config.\n") 40 | os.Exit(ExitNormal) 41 | 42 | } 43 | -------------------------------------------------------------------------------- /internal/aem/generate/components.go: -------------------------------------------------------------------------------- 1 | package generate 2 | 3 | import ( 4 | "github.com/BurntSushi/toml" 5 | "github.com/jlentink/aem/internal/output" 6 | "io/ioutil" 7 | "os" 8 | ) 9 | 10 | const templateDir = "templates" 11 | const settingsFile = "settings.toml" 12 | 13 | // ListComponents in templates folder 14 | func ListComponents() ([]*Component, error) { 15 | templateRoot, err := os.Getwd() 16 | if err != nil { 17 | return nil, err 18 | } 19 | 20 | components := make([]*Component, 0) 21 | 22 | templateRoot = templateRoot + "/" + templateDir 23 | entries, err := ioutil.ReadDir(templateRoot) 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | for _, entry := range entries { 29 | if entry.IsDir() { 30 | if _, err := os.Stat(templateRoot + "/" + entry.Name() + "/" + settingsFile); os.IsNotExist(err) { 31 | output.Printf(output.VERBOSE, "Did not find settingsfile for %s\n", entry.Name()) 32 | continue 33 | } 34 | 35 | component := Component{} 36 | _, err := toml.DecodeFile(templateRoot+"/"+entry.Name()+"/"+settingsFile, &component) 37 | if err != nil { 38 | output.Printf(output.VERBOSE, "Error in settings file of %s - %s\n", entry.Name(), err.Error()) 39 | continue 40 | } 41 | component.SourcePath = templateRoot + "/" + entry.Name() 42 | components = append(components, &component) 43 | } else { 44 | continue 45 | } 46 | } 47 | return components, nil 48 | } 49 | -------------------------------------------------------------------------------- /internal/commands/setupcheck.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | ct "github.com/daviddengcn/go-colortext" 6 | "github.com/jlentink/aem/internal/cli/setup" 7 | "github.com/spf13/cobra" 8 | "os" 9 | ) 10 | 11 | type commandSetupCheck struct { 12 | verbose bool 13 | } 14 | 15 | func (c *commandSetupCheck) setup() *cobra.Command { 16 | cmd := &cobra.Command{ 17 | Use: "setup-check", 18 | Aliases: []string{"setup"}, 19 | Short: "Check if all needed binaries are available for all functionality", 20 | PreRun: c.preRun, 21 | Run: c.run, 22 | } 23 | 24 | return cmd 25 | } 26 | 27 | func (c *commandSetupCheck) preRun(cmd *cobra.Command, args []string) { 28 | c.verbose, _ = cmd.Flags().GetBool("verbose") 29 | } 30 | 31 | func (c *commandSetupCheck) printStatus(bin setup.Description) { 32 | color := ct.Green 33 | statusMsg := " FOUND " 34 | if !bin.Found { 35 | statusMsg = "MISSING" 36 | switch bin.Required { 37 | case setup.Required: 38 | color = ct.Red 39 | case setup.Optional: 40 | color = ct.Yellow 41 | } 42 | } 43 | fmt.Print("[ ") 44 | ct.ChangeColor(color, false, ct.None, false) 45 | fmt.Print(statusMsg) 46 | ct.ResetColor() 47 | fmt.Printf(" ] %s - %s\n", bin.Bin, bin.Description) 48 | } 49 | func (c *commandSetupCheck) run(cmd *cobra.Command, args []string) { 50 | for _, bin := range setup.Check() { 51 | c.printStatus(bin) 52 | } 53 | os.Exit(ExitNormal) 54 | } 55 | -------------------------------------------------------------------------------- /internal/commands/cloudmanagerPush.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/aem/cloudmanager" 5 | "github.com/jlentink/aem/internal/output" 6 | "github.com/spf13/cobra" 7 | "os" 8 | ) 9 | 10 | type commandCloudManagerPush struct { 11 | verbose bool 12 | instanceName string 13 | toInstanceName string 14 | toGroup string 15 | install bool 16 | force bool 17 | cPackage []string 18 | } 19 | 20 | func (c *commandCloudManagerPush) setup() *cobra.Command { 21 | c.install = false 22 | c.force = false 23 | cmd := &cobra.Command{ 24 | Use: "push", 25 | Short: "Push current branch to Adobe Cloud manager GIT", 26 | PreRun: c.preRun, 27 | Run: c.run, 28 | } 29 | 30 | return cmd 31 | } 32 | 33 | func (c *commandCloudManagerPush) preRun(cmd *cobra.Command, args []string) { 34 | c.verbose, _ = cmd.Flags().GetBool("verbose") 35 | output.SetVerbose(c.verbose) 36 | 37 | ConfigCheckListProjects() 38 | RegisterProject() 39 | } 40 | 41 | func (c *commandCloudManagerPush) run(cmd *cobra.Command, args []string) { 42 | cnf, err := getConfig() 43 | if err != nil { 44 | output.Printf(output.NORMAL, "Error getting config", err.Error()) 45 | os.Exit(ExitError) 46 | } 47 | 48 | err = cloudmanager.GitPush(cnf) 49 | if err != nil { 50 | output.Printf(output.NORMAL, "Error pushing to adobe: %s.", err.Error()) 51 | os.Exit(ExitError) 52 | 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /internal/commands/commands.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/output" 5 | "github.com/spf13/cobra" 6 | "os" 7 | ) 8 | 9 | var ( 10 | verbose bool 11 | // Project - Global set project 12 | Project string 13 | commands = []Command{ 14 | &commandVersion{}, 15 | &commandInit{}, 16 | &commandStart{}, 17 | &commandStop{}, 18 | &commandOpen{}, 19 | &commandLog{}, 20 | &commandShellCompletion{}, 21 | &commandSetupCheck{}, 22 | &commandSystemInformation{}, 23 | &commandDeploy{}, 24 | &commandBuild{}, 25 | &commandPullContent{}, 26 | &commandPassword{}, 27 | &commandBundle{}, 28 | &commandPackage{}, 29 | &commandOak{}, 30 | &commandIndex{}, 31 | &commandActivation{}, 32 | &commandProjects{}, 33 | &commandGenerate{}, 34 | &commandInvalidate{}, 35 | &commandDestroy{}, 36 | &commandCdn{}, 37 | //&commandCloudManager{}, 38 | } 39 | rootCmd = &cobra.Command{Use: "aem"} 40 | ) 41 | 42 | // Execute init commands 43 | func Execute() { 44 | rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output") 45 | rootCmd.PersistentFlags().StringVarP(&Project, "project", "P", "", "Run command for project. (if not current working directory)") 46 | for _, cmd := range commands { 47 | rootCmd.AddCommand(cmd.setup()) 48 | } 49 | err := rootCmd.Execute() 50 | if err != nil { 51 | output.Printf(output.NORMAL, "Could not execute root command.\n") 52 | os.Exit(ExitError) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /internal/commands/packageRebuild.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/aem" 5 | "github.com/jlentink/aem/internal/output" 6 | "github.com/spf13/cobra" 7 | "os" 8 | ) 9 | 10 | type commandPackageRebuild struct { 11 | verbose bool 12 | instanceName string 13 | packageName string 14 | } 15 | 16 | func (c *commandPackageRebuild) setup() *cobra.Command { 17 | cmd := &cobra.Command{ 18 | Use: "rebuild", 19 | Short: "package rebuild", 20 | PreRun: c.preRun, 21 | Run: c.run, 22 | } 23 | cmd.Flags().StringVarP(&c.instanceName, "name", "n", aem.GetDefaultInstanceName(), "Instance to rebuild package on") 24 | cmd.Flags().StringVarP(&c.packageName, "package", "p", ``, "Package to rebuild") 25 | cmd.MarkFlagRequired("name") // nolint: errcheck 26 | return cmd 27 | } 28 | 29 | func (c *commandPackageRebuild) preRun(cmd *cobra.Command, args []string) { 30 | c.verbose, _ = cmd.Flags().GetBool("verbose") 31 | output.SetVerbose(c.verbose) 32 | 33 | ConfigCheckListProjects() 34 | RegisterProject() 35 | } 36 | 37 | func (c *commandPackageRebuild) run(cmd *cobra.Command, args []string) { 38 | _, i, errorString, err := getConfigAndInstance(c.instanceName) 39 | if err != nil { 40 | output.Printf(output.NORMAL, errorString, err.Error()) 41 | os.Exit(ExitError) 42 | } 43 | 44 | if len(c.packageName) <= 0 { 45 | rebuildPackageSearch(i) 46 | os.Exit(ExitNormal) 47 | } 48 | 49 | rebuildPackage(i, c.packageName) 50 | 51 | } 52 | -------------------------------------------------------------------------------- /internal/commands/packageInstall.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/aem" 5 | "github.com/jlentink/aem/internal/output" 6 | "github.com/spf13/cobra" 7 | "os" 8 | ) 9 | 10 | type commandPackageInstall struct { 11 | verbose bool 12 | instanceName string 13 | packageName []string 14 | } 15 | 16 | func (c *commandPackageInstall) setup() *cobra.Command { 17 | cmd := &cobra.Command{ 18 | Use: "install", 19 | Short: "Install uploaded package", 20 | PreRun: c.preRun, 21 | Run: c.run, 22 | } 23 | cmd.Flags().StringVarP(&c.instanceName, "name", "n", aem.GetDefaultInstanceName(), "Instance to rebuild package on") 24 | cmd.Flags().StringArrayVarP(&c.packageName, "package", "p", []string{}, "Package to install (allowed multiple)") 25 | return cmd 26 | } 27 | 28 | func (c *commandPackageInstall) preRun(cmd *cobra.Command, args []string) { 29 | c.verbose, _ = cmd.Flags().GetBool("verbose") 30 | output.SetVerbose(c.verbose) 31 | 32 | ConfigCheckListProjects() 33 | RegisterProject() 34 | } 35 | 36 | func (c *commandPackageInstall) run(cmd *cobra.Command, args []string) { 37 | _, i, errorString, err := getConfigAndInstance(c.instanceName) 38 | if err != nil { 39 | output.Printf(output.NORMAL, errorString, err.Error()) 40 | os.Exit(ExitError) 41 | } 42 | 43 | if len(c.packageName) <= 0 { 44 | installPackageSearch(i) 45 | os.Exit(ExitNormal) 46 | } 47 | 48 | for _, packageName := range c.packageName { 49 | installPackage(i, packageName) 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /internal/aem/generate/template_test.go: -------------------------------------------------------------------------------- 1 | package generate 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func Test_ParseTemplate(t *testing.T) { 8 | type args struct { 9 | tpl string 10 | vars map[string]string 11 | } 12 | tests := []struct { 13 | name string 14 | args args 15 | want string 16 | wantErr bool 17 | }{ 18 | { 19 | name: "Test simple template", 20 | args: args{tpl: "{{.Hello}} world.", vars: map[string]string{"Hello": "Hello"}}, 21 | want: "Hello world.", 22 | wantErr: false, 23 | }, 24 | { 25 | name: "Test uppercase template", 26 | args: args{tpl: "{{UC .Hello}} world.", vars: map[string]string{"Hello": "Hello"}}, 27 | want: "HELLO world.", 28 | wantErr: false, 29 | }, 30 | { 31 | name: "Test uppercase template", 32 | args: args{tpl: "{{LC .Hello}} world.", vars: map[string]string{"Hello": "Hello"}}, 33 | want: "hello world.", 34 | wantErr: false, 35 | }, 36 | { 37 | name: "Test uppercase template", 38 | args: args{tpl: "{{BLA .Hello}} world.", vars: map[string]string{"Hello": "Hello"}}, 39 | want: "", 40 | wantErr: true, 41 | }, 42 | } 43 | for _, tt := range tests { 44 | t.Run(tt.name, func(t *testing.T) { 45 | got, err := ParseTemplate(tt.args.tpl, tt.args.vars) 46 | if (err != nil) != tt.wantErr { 47 | t.Errorf("parseTemplate() error = %v, wantErr %v", err, tt.wantErr) 48 | return 49 | } 50 | if got != tt.want { 51 | t.Errorf("parseTemplate() got = %v, want %v", got, tt.want) 52 | } 53 | }) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /internal/aem/dispatcher/invalidate.go: -------------------------------------------------------------------------------- 1 | package dispatcher 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jlentink/aem/internal/aem" 6 | "github.com/jlentink/aem/internal/aem/objects" 7 | "github.com/jlentink/aem/internal/http" 8 | "github.com/jlentink/aem/internal/output" 9 | ) 10 | 11 | const ( 12 | invalidateURL = "/dispatcher/invalidate.cache" 13 | ) 14 | 15 | // Invalidate sent invalidate to dispatcher 16 | func Invalidate(i *objects.Instance, path string) error { 17 | if !aem.Cnf.ValidateSSL { 18 | http.DisableSSLValidation() 19 | } 20 | 21 | header := []http.Header{ 22 | {Key: "Host", Value: i.Hostname}, 23 | {Key: "CQ-Action", Value: "Activate"}, 24 | {Key: "Content-Length", Value: "0"}, 25 | {Key: "Content-Type", Value: "application/octet-stream"}, 26 | {Key: "CQ-Path", Value: path}, 27 | {Key: "CQ-Handle", Value: path}, 28 | } 29 | _, err := http.GetPlainWithHeaders(i.URLIPString()+invalidateURL, i.Username, i.Password, header) 30 | if err != nil { 31 | return fmt.Errorf("could not invalidate path: %s", err.Error()) 32 | } 33 | 34 | return nil 35 | } 36 | 37 | // InvalidateAll sent invalidate to all dispatchers 38 | func InvalidateAll(is []objects.Instance, p []string) bool { 39 | var status = true 40 | for idx, i := range is { 41 | for _, path := range p { 42 | output.Printf(output.NORMAL, "\U0001F5D1 Invalidating: %s (%s)\n", path, i.Name) 43 | err := Invalidate(&is[idx], path) 44 | if err != nil { 45 | output.Printf(output.NORMAL, "Could not invalidate path: %s\n", err.Error()) 46 | status = false 47 | } 48 | } 49 | } 50 | return status 51 | } 52 | -------------------------------------------------------------------------------- /internal/aem/objects/SystemInformation.go: -------------------------------------------------------------------------------- 1 | package objects 2 | 3 | //SystemInformation descriptive struct 4 | type SystemInformation struct { 5 | HealthChecks interface{} `json:"Health Checks"` 6 | Instance struct { 7 | AdobeExperienceManager string `json:"Adobe Experience Manager"` 8 | RunModes string `json:"Run Modes"` 9 | InstanceUpSince string `json:"Instance Up Since"` 10 | } `json:"Instance"` 11 | Repository struct { 12 | ApacheJackrabbitOak string `json:"Apache Jackrabbit Oak"` 13 | NodeStore string `json:"Node Store"` 14 | RepositorySize string `json:"Repository Size"` 15 | FileDataStore string `json:"File Data Store"` 16 | } `json:"Repository"` 17 | MaintenanceTasks interface{} `json:"Maintenance Tasks"` 18 | SystemInformation struct { 19 | MacOSX string `json:"Mac OS X"` 20 | Linux string `json:"Linux"` 21 | Windows string `json:"Windows"` 22 | CurrentOS string 23 | SystemLoadAverage string `json:"System Load Average"` 24 | UsableDiskSpace string `json:"Usable Disk Space"` 25 | MaximumHeap string `json:"Maximum Heap"` 26 | } `json:"System Information"` 27 | EstimatedNodeCounts struct { 28 | Total string `json:"Total"` 29 | Tags string `json:"Tags"` 30 | Assets string `json:"Assets"` 31 | Authorizables string `json:"Authorizables"` 32 | Pages string `json:"Pages"` 33 | } `json:"Estimated Node Counts"` 34 | ReplicationAgents interface{} `json:"Replication Agents"` 35 | DistributionAgents interface{} `json:"Distribution Agents"` 36 | } 37 | -------------------------------------------------------------------------------- /internal/aem/start_test.go: -------------------------------------------------------------------------------- 1 | package aem 2 | 3 | import ( 4 | "os/user" 5 | "testing" 6 | ) 7 | 8 | func TestGetJar(t *testing.T) { 9 | type args struct { 10 | forceDownload bool 11 | } 12 | tests := []struct { 13 | name string 14 | args args 15 | want uint64 16 | wantErr bool 17 | }{ 18 | // TODO: Add test cases. 19 | } 20 | for _, tt := range tests { 21 | t.Run(tt.name, func(t *testing.T) { 22 | //got, err := GetJar(tt.args.forceDownload, ) 23 | //if (err != nil) != tt.wantErr { 24 | // t.Errorf("GetJar() error = %v, wantErr %v", err, tt.wantErr) 25 | // return 26 | //} 27 | //if got != tt.want { 28 | // t.Errorf("GetJar() = %v, want %v", got, tt.want) 29 | //} 30 | }) 31 | } 32 | } 33 | 34 | func TestAllowUserStart(t *testing.T) { 35 | type args struct { 36 | allow bool 37 | } 38 | tests := []struct { 39 | name string 40 | args args 41 | want bool 42 | uid string 43 | }{ 44 | { 45 | name: "Always allow", 46 | args: args{allow: true}, 47 | want: true, 48 | uid: "501", 49 | }, 50 | { 51 | name: "Block root", 52 | args: args{allow: false}, 53 | want: false, 54 | uid: "0", 55 | }, 56 | { 57 | name: "Allow normal user", 58 | args: args{allow: false}, 59 | want: true, 60 | uid: "501", 61 | }, 62 | } 63 | for _, tt := range tests { 64 | t.Run(tt.name, func(t *testing.T) { 65 | u, _ = user.Current() 66 | u.Uid = tt.uid 67 | if got := AllowUserStart(tt.args.allow); got != tt.want { 68 | t.Errorf("AllowUserStart() = %v, want %v", got, tt.want) 69 | } 70 | u = nil 71 | }) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /internal/aem/http.go: -------------------------------------------------------------------------------- 1 | package aem 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jlentink/aem/internal/aem/objects" 6 | "github.com/jlentink/aem/internal/http" 7 | "net/url" 8 | ) 9 | 10 | const ( 11 | // URLSystemInformation system overview URL 12 | URLSystemInformation = "/libs/granite/operations/content/systemoverview/export.json" 13 | ) 14 | 15 | // URL for instance (*url.URL) 16 | func URL(i *objects.Instance, uri string) (*url.URL, error) { 17 | u, err := url.Parse(URLString(i, false) + uri) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | pwd, err := i.GetPassword() 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | u.User = url.UserPassword(i.Username, pwd) 28 | return u, nil 29 | } 30 | 31 | // URLString for instance 32 | func URLString(i *objects.Instance, ip bool) string { 33 | if ip { 34 | return fmt.Sprintf("%s://%s:%d", i.Protocol, i.IP, i.Port) 35 | } 36 | return fmt.Sprintf("%s://%s:%d", i.Protocol, i.Hostname, i.Port) 37 | } 38 | 39 | // PasswordURL with credentials for instance 40 | func PasswordURL(i objects.Instance, useKeyring bool) (string, error) { 41 | password, err := GetPasswordForInstance(i, useKeyring) 42 | if err != nil { 43 | return ``, nil 44 | } 45 | return fmt.Sprintf("%s://%s:%s@%s:%d", i.Protocol, url.QueryEscape(i.Username), url.QueryEscape(password), i.Hostname, i.Port), nil 46 | } 47 | 48 | // GetFromInstance Perform a get request towards an instance 49 | func GetFromInstance(i *objects.Instance, uri string) ([]byte, error) { 50 | if !Cnf.ValidateSSL { 51 | http.DisableSSLValidation() 52 | } 53 | 54 | u, err := URL(i, uri) 55 | if err != nil { 56 | return []byte{}, err 57 | } 58 | 59 | return http.Get(u) 60 | } 61 | -------------------------------------------------------------------------------- /internal/http/progressReporter.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "fmt" 5 | "github.com/dustin/go-humanize" 6 | "github.com/k0kubun/go-ansi" 7 | "github.com/schollz/progressbar/v2" 8 | "io" 9 | ) 10 | 11 | // 12 | // ProgressReporter is used to display download progress 13 | // 14 | type progressReporter struct { 15 | r io.Reader 16 | w io.Writer 17 | progress int64 18 | totalSize uint64 19 | label string 20 | bar *progressbar.ProgressBar 21 | } 22 | 23 | func (pr *progressReporter) initProgressBar() { 24 | pr.bar = progressbar.NewOptions64(int64(pr.totalSize), 25 | progressbar.OptionSetWriter(ansi.NewAnsiStdout()), 26 | progressbar.OptionEnableColorCodes(true), 27 | progressbar.OptionSetDescription(pr.label), 28 | progressbar.OptionSetTheme(progressbar.Theme{ 29 | Saucer: "[green]=[reset]", 30 | SaucerPadding: "-", 31 | SaucerHead: "[green]>[reset]", 32 | BarStart: "[", BarEnd: "]", 33 | }), 34 | progressbar.OptionSetWidth(45), 35 | ) 36 | } 37 | 38 | func (pr *progressReporter) Read(p []byte) (int, error) { 39 | n, err := pr.r.Read(p) 40 | pr.progress += int64(n) 41 | pr.report(int64(n)) 42 | return n, err 43 | } 44 | 45 | func (pr *progressReporter) Write(p []byte) (int, error) { 46 | //fmt.Printf("sdsa %v", pr.totalSize) 47 | n, err := pr.w.Write(p) 48 | pr.report(int64(n)) 49 | return n, err 50 | } 51 | 52 | func (pr *progressReporter) report(progress int64) { 53 | if pr.totalSize > 0 { 54 | if pr.bar == nil { 55 | pr.initProgressBar() 56 | } 57 | pr.bar.Add64(progress) // nolint: errcheck 58 | } else { 59 | fmt.Printf("\r%s... %s complete\r", pr.label, humanize.Bytes(uint64(pr.progress))) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /internal/http/general.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "crypto/tls" 5 | "fmt" 6 | "net/http" 7 | "net/url" 8 | ) 9 | 10 | const configNoCache = "no-cache" 11 | 12 | // URLS aem 13 | const ( 14 | URLSystemInformation = "/libs/granite/operations/content/systemoverview/export.json" 15 | URLActivateTree = "/etc/replication/treeactivation.html" 16 | URLBundles = "/system/console/bundles" 17 | URLRebuildPackage = "/crx/packmgr/service/.json%s?cmd=build" 18 | URLBundlePage = "/system/console/bundles/%s" 19 | URLReplication = "/bin/replicate.json" 20 | URLPackageList = "/crx/packmgr/list.jsp" 21 | URLPackageEndpoint = "/crx/packmgr/service.jsp" 22 | 23 | ServiceName = "aem-cli" 24 | 25 | JarContentType = "application/java-archive" 26 | ApplicationFormEncode = "application/x-www-form-urlencoded" 27 | ) 28 | 29 | //DisableSSLValidation Disables SSL validation 30 | func DisableSSLValidation() { 31 | http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} 32 | } 33 | 34 | // URLToURLString convert the url to string 35 | func URLToURLString(u *url.URL) string { 36 | return fmt.Sprintf("%s://%s%s", u.Scheme, u.Host, u.RequestURI()) 37 | } 38 | 39 | func setupPlainToURL(uri string, username string, password string) (*url.URL, error){ 40 | URL, err := url.Parse(uri) 41 | 42 | if username != "" || password != "" { 43 | URL.User = url.UserPassword(username, password) 44 | 45 | } 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | if username != "" || password != "" { 51 | URL.User = url.UserPassword(username, password) 52 | 53 | } 54 | if err != nil { 55 | return nil, err 56 | } 57 | return URL, nil 58 | } -------------------------------------------------------------------------------- /internal/commands/cloudmanagerGitAuthenticate.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "github.com/jlentink/aem/internal/aem/cloudmanager" 7 | "github.com/jlentink/aem/internal/output" 8 | "github.com/spf13/cobra" 9 | "os" 10 | ) 11 | 12 | type commandCloudManagerGitAuthenticate struct { 13 | verbose bool 14 | instanceName string 15 | toInstanceName string 16 | toGroup string 17 | install bool 18 | force bool 19 | cPackage []string 20 | } 21 | 22 | func (c *commandCloudManagerGitAuthenticate) setup() *cobra.Command { 23 | c.install = false 24 | c.force = false 25 | cmd := &cobra.Command{ 26 | Use: "git-auth", 27 | Short: "Add git credentials.", 28 | PreRun: c.preRun, 29 | Run: c.run, 30 | } 31 | 32 | return cmd 33 | } 34 | 35 | func (c *commandCloudManagerGitAuthenticate) preRun(cmd *cobra.Command, args []string) { 36 | c.verbose, _ = cmd.Flags().GetBool("verbose") 37 | output.SetVerbose(c.verbose) 38 | 39 | ConfigCheckListProjects() 40 | RegisterProject() 41 | } 42 | 43 | func (c *commandCloudManagerGitAuthenticate) run(cmd *cobra.Command, args []string) { 44 | cnf, err := getConfig() 45 | if err != nil { 46 | output.Printf(output.NORMAL, "Error getting config", err.Error()) 47 | os.Exit(ExitError) 48 | } 49 | 50 | reader := bufio.NewReader(os.Stdin) 51 | fmt.Print("Enter username: ") 52 | usr, _ := reader.ReadString('\n') 53 | 54 | fmt.Print("Enter password: ") 55 | pwd, _ := reader.ReadString('\n') 56 | 57 | err = cloudmanager.GitSetAuthentication(usr, pwd, cnf) 58 | if err != nil { 59 | output.Printf(output.NORMAL, "Error setting the credentials. %s", err.Error()) 60 | os.Exit(ExitError) 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /internal/packageproperties/general.go: -------------------------------------------------------------------------------- 1 | package packageproperties 2 | 3 | import ( 4 | "archive/zip" 5 | "encoding/xml" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "os" 10 | ) 11 | 12 | const ( 13 | propertiesPath = "META-INF/vault/properties.xml" 14 | ) 15 | 16 | var ( 17 | keyValues map[string]string 18 | ) 19 | 20 | // Open properties file of package 21 | func Open(location string) (*Properties, error) { 22 | reader, err := zip.OpenReader(location) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | for _, file := range reader.File { 28 | if file.Name == propertiesPath { 29 | properties, err := file.Open() 30 | if err != nil { 31 | return nil, err 32 | } 33 | defer properties.Close() // nolint: errcheck 34 | return parseProperties(properties) 35 | } 36 | } 37 | return nil, fmt.Errorf("could not find properties file in zip") 38 | } 39 | 40 | // OpenXML properties file for reading 41 | func OpenXML(location string) (*Properties, error) { 42 | if _, err := os.Stat(location); os.IsNotExist(err) { 43 | return nil, fmt.Errorf("could not find file at: %s", err) 44 | } 45 | 46 | properties, err := os.Open(location) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | return parseProperties(properties) 52 | } 53 | 54 | func parseProperties(r io.Reader) (*Properties, error) { 55 | keyValues = make(map[string]string) 56 | d, err := ioutil.ReadAll(r) 57 | if err != nil { 58 | return nil, err 59 | } 60 | rawXML := propertiesXML{} 61 | err = xml.Unmarshal(d, &rawXML) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | for _, entry := range rawXML.Entry { 67 | keyValues[entry.Key] = entry.Text 68 | } 69 | 70 | return &Properties{Property: keyValues}, nil 71 | } 72 | -------------------------------------------------------------------------------- /internal/commands/bundleList.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jedib0t/go-pretty/table" 5 | "github.com/jlentink/aem/internal/aem" 6 | "github.com/jlentink/aem/internal/aem/bundle" 7 | "github.com/jlentink/aem/internal/output" 8 | "github.com/spf13/cobra" 9 | "os" 10 | ) 11 | 12 | type commandBundleList struct { 13 | verbose bool 14 | instanceName string 15 | } 16 | 17 | func (c *commandBundleList) setup() *cobra.Command { 18 | cmd := &cobra.Command{ 19 | Use: "list", 20 | Short: "List bundles", 21 | PreRun: c.preRun, 22 | Run: c.run, 23 | } 24 | cmd.Flags().StringVarP(&c.instanceName, "name", "n", aem.GetDefaultInstanceName(), "Instance to list bundles off") 25 | return cmd 26 | } 27 | 28 | func (c *commandBundleList) preRun(cmd *cobra.Command, args []string) { 29 | c.verbose, _ = cmd.Flags().GetBool("verbose") 30 | output.SetVerbose(c.verbose) 31 | 32 | ConfigCheckListProjects() 33 | RegisterProject() 34 | } 35 | 36 | func (c *commandBundleList) run(cmd *cobra.Command, args []string) { 37 | _, i, errorString, err := getConfigAndInstance(c.instanceName) 38 | if err != nil { 39 | output.Printf(output.NORMAL, errorString, err.Error()) 40 | os.Exit(ExitError) 41 | } 42 | 43 | t := table.NewWriter() 44 | t.SetOutputMirror(os.Stdout) 45 | t.AppendHeader(table.Row{"ID", "Name", "Symbolic name", "Version", "State"}) 46 | bundles, err := bundle.List(i) 47 | if err != nil { 48 | output.Printf(output.NORMAL, "Could not get list from instance: %s", err.Error()) 49 | os.Exit(ExitError) 50 | 51 | } 52 | for _, cBundle := range bundles { 53 | t.AppendRow([]interface{}{cBundle.ID, cBundle.Name, cBundle.SymbolicName, cBundle.Version, cBundle.State}) 54 | } 55 | t.Render() 56 | } 57 | -------------------------------------------------------------------------------- /internal/commands/indexes.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jedib0t/go-pretty/table" 5 | "github.com/jlentink/aem/internal/aem" 6 | "github.com/jlentink/aem/internal/aem/indexes" 7 | "github.com/jlentink/aem/internal/output" 8 | "github.com/spf13/cobra" 9 | "os" 10 | ) 11 | 12 | type commandIndexes struct { 13 | verbose bool 14 | instanceName string 15 | } 16 | 17 | func (c *commandIndexes) setup() *cobra.Command { 18 | cmd := &cobra.Command{ 19 | Use: "indexes", 20 | Aliases: []string{"list"}, 21 | Short: "Show indexes on instance", 22 | PreRun: c.preRun, 23 | Run: c.run, 24 | } 25 | cmd.Flags().StringVarP(&c.instanceName, "name", "n", aem.GetDefaultInstanceName(), "Instance to stop") 26 | return cmd 27 | } 28 | 29 | func (c *commandIndexes) preRun(cmd *cobra.Command, args []string) { 30 | c.verbose, _ = cmd.Flags().GetBool("verbose") 31 | output.SetVerbose(verbose) 32 | 33 | ConfigCheckListProjects() 34 | RegisterProject() 35 | } 36 | 37 | func (c *commandIndexes) run(cmd *cobra.Command, args []string) { 38 | _, i, errorString, err := getConfigAndInstance(c.instanceName) 39 | if err != nil { 40 | output.Printf(output.NORMAL, errorString, err.Error()) 41 | os.Exit(ExitError) 42 | } 43 | 44 | searchIndexes, err := indexes.GetIndexes(i) 45 | if err != nil { 46 | output.Printf(output.NORMAL, "Error retrieving indexes. (%s)", err.Error()) 47 | os.Exit(ExitError) 48 | } 49 | 50 | t := table.NewWriter() 51 | t.SetOutputMirror(os.Stdout) 52 | t.AppendHeader(table.Row{"#", "Name", "Reindex count", "info"}) 53 | for i, index := range searchIndexes { 54 | t.AppendRow([]interface{}{i, index.Name, index.ReindexCount, index.Info}) 55 | } 56 | 57 | t.Render() 58 | 59 | } 60 | -------------------------------------------------------------------------------- /internal/aem/cloudmanager/git.go: -------------------------------------------------------------------------------- 1 | package cloudmanager 2 | 3 | import ( 4 | "fmt" 5 | "github.com/go-git/go-git/v5" 6 | "github.com/go-git/go-git/v5/config" 7 | "github.com/jlentink/aem/internal/aem/objects" 8 | "github.com/jlentink/aem/internal/cli/project" 9 | "github.com/zalando/go-keyring" 10 | "os" 11 | ) 12 | 13 | // AdobeCloudManagerRemote ... 14 | const AdobeCloudManagerRemote = "AdobeCloudManagerRemote" 15 | 16 | func setupGit() (*git.Repository, error){ 17 | cwd, err := project.GetWorkDir() 18 | if err != nil { 19 | return nil, err 20 | } 21 | repo, err := git.PlainOpen(cwd) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | return repo, nil 27 | } 28 | 29 | func setupRemote(repo *git.Repository, cnf *objects.Config) error { 30 | remotes, err := repo.Remotes() 31 | if err != nil { 32 | return err 33 | } 34 | for _, remote := range remotes { 35 | if remote.Config().Name == AdobeCloudManagerRemote { 36 | return nil 37 | } 38 | } 39 | 40 | _, err = repo.CreateRemote(&config.RemoteConfig{ 41 | Name: AdobeCloudManagerRemote, 42 | URLs: []string{cnf.CloudManagerGit}, 43 | }) 44 | 45 | return err 46 | } 47 | 48 | func getGitAuth(){ 49 | //http.BasicAuth{Username: , Password: } 50 | } 51 | 52 | // GitPush ... 53 | func GitPush(cnf *objects.Config) error { 54 | 55 | repo, err := setupGit() 56 | if err != nil { 57 | return err 58 | } 59 | 60 | err = setupRemote(repo, cnf) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | err = repo.Push(&git.PushOptions{ 66 | RemoteName: AdobeCloudManagerRemote, 67 | //Auth: , 68 | Progress: os.Stdout, 69 | }) 70 | 71 | return err 72 | } 73 | 74 | // GitSetAuthentication ... 75 | func GitSetAuthentication(username, password string, cnf *objects.Config) error { 76 | return keyring.Set(fmt.Sprintf("%s-%s", cnf.ProjectName,AdobeCloudManagerRemote), username, password) 77 | } -------------------------------------------------------------------------------- /internal/output/output.go: -------------------------------------------------------------------------------- 1 | package output 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | ) 8 | 9 | const ( 10 | // NORMAL Output in normal mode 11 | NORMAL = iota 12 | 13 | // VERBOSE Output in verbose mode 14 | VERBOSE 15 | ) 16 | 17 | var logLevel = NORMAL 18 | 19 | // SetLevel Set log level (NORMAL, VERBOSE) 20 | func SetLevel(level int) { 21 | logLevel = level 22 | } 23 | 24 | // SetVerbose set verbose level 25 | func SetVerbose(v bool) { 26 | if v { 27 | SetLevel(VERBOSE) 28 | } 29 | } 30 | 31 | // Print formats using the default formats for its operands and writes to standard output. 32 | // Spaces are added between operands when neither is a string. 33 | // It returns the number of bytes written and any write error encountered. 34 | // Only displays when log level is high enough 35 | func Print(l int, o string) (int, error) { 36 | if logLevel >= l { 37 | return fmt.Print(o) 38 | } 39 | return 0, nil 40 | } 41 | 42 | // Printf formats according to a format specifier and writes to standard output. 43 | // It returns the number of bytes written and any write error encountered. 44 | // Only displays when log level is high enough 45 | func Printf(l int, o string, a ...interface{}) (int, error) { 46 | if logLevel >= l { 47 | return fmt.Printf(o, a...) 48 | } 49 | return 0, nil 50 | } 51 | 52 | // PrintListing formats a array into a list 53 | func PrintListing(key, valuesString string, trail bool) { 54 | values := strings.Split(valuesString, ",") 55 | fmt.Printf("- %s:\n", key) 56 | for i, value := range values { 57 | fmt.Printf("\t %d) %s\n", i+1, strings.TrimSpace(value)) 58 | } 59 | if trail { 60 | fmt.Printf("\n") 61 | } 62 | } 63 | 64 | // UnixTime Format time 65 | func UnixTime(timestamp int64) *time.Time { 66 | if timestamp == 0 { 67 | return nil 68 | } 69 | tm := time.Unix(timestamp/1000, 0) 70 | theTime := tm.UTC() 71 | return &theTime 72 | } 73 | -------------------------------------------------------------------------------- /internal/cli/setup/check.go: -------------------------------------------------------------------------------- 1 | package setup 2 | 3 | import ( 4 | "os/exec" 5 | "runtime" 6 | "strings" 7 | ) 8 | 9 | // Requirement of bin 10 | const ( 11 | Required = int(iota) 12 | Optional 13 | ) 14 | 15 | var ( 16 | general = []Description{ 17 | {Bin: "java", Description: "needed to run AEM and run-oak. (Crucial)", Required: Required}, 18 | {Bin: "vlt", Description: "needed for all vlt actions", Required: Optional}, 19 | {Bin: "aemsync", Description: "needed to sync frontend code with AEM instance", Required: Optional}, 20 | {Bin: "git", Description: "Git version control for versioning", Required: Optional}, 21 | {Bin: "mvn", Description: "Maven build tool", Required: Optional}, 22 | {Bin: "docker", Description: "Docker to for running dispatchers", Required: Optional}, 23 | } 24 | darwin = []Description{ 25 | {Bin: "kill", Description: "needed to run stop. (Crucial)", Required: Required}, 26 | {Bin: "open", Description: "needed to open the browser with the correct URL.", Required: Optional}, 27 | } 28 | linux = []Description{ 29 | {Bin: "kill", Description: "needed to run stop. (Crucial)", Required: Required}, 30 | } 31 | windows = []Description{ 32 | {Bin: "rundll32", Description: "needed to open the browser with the correct URL.", Required: Optional}, 33 | } 34 | ) 35 | 36 | func osCombine() []Description { 37 | switch strings.ToLower(runtime.GOOS) { 38 | case "darwin": 39 | return append(general, darwin...) 40 | case "linux": 41 | return append(general, linux...) 42 | case "windows": 43 | return append(general, windows...) 44 | default: 45 | return general 46 | } 47 | } 48 | 49 | func check(bin Description) Description { 50 | _, err := exec.LookPath(bin.Bin) 51 | bin.Found = nil == err 52 | return bin 53 | } 54 | 55 | // Check bin if it is available 56 | func Check() []Description { 57 | binaries := osCombine() 58 | for i, bin := range binaries { 59 | binaries[i] = check(bin) 60 | } 61 | return binaries 62 | } 63 | -------------------------------------------------------------------------------- /internal/commands/build.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/aem" 5 | "github.com/jlentink/aem/internal/output" 6 | "os" 7 | 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | type commandBuild struct { 12 | verbose bool 13 | versionOnly bool 14 | productionBuild bool 15 | skipTests bool 16 | skipFrontend bool 17 | skipCheckStyle bool 18 | } 19 | 20 | func (c *commandBuild) setup() *cobra.Command { 21 | cmd := &cobra.Command{ 22 | Use: "build", 23 | Short: "Build application", 24 | Aliases: []string{}, 25 | PreRun: c.preRun, 26 | Run: c.run, 27 | } 28 | 29 | cmd.Flags().BoolVarP(&c.productionBuild, "production-build", "B", false, 30 | "Flush after deploy") 31 | cmd.Flags().BoolVarP(&c.skipTests, "skip-tests", "t", false, 32 | "Skip tests") 33 | cmd.Flags().BoolVarP(&c.skipFrontend, "skip-frontend", "F", false, 34 | "Skip frontend build") 35 | cmd.Flags().BoolVarP(&c.skipCheckStyle, "skip-checkstyle", "c", false, 36 | "Skip checkstyle") 37 | cmd.Flags().BoolVarP(&c.versionOnly, "version", "V", false, 38 | "Don't build version only.") 39 | 40 | return cmd 41 | } 42 | 43 | func (c *commandBuild) preRun(cmd *cobra.Command, args []string) { 44 | c.verbose, _ = cmd.Flags().GetBool("verbose") 45 | 46 | output.SetVerbose(verbose) 47 | 48 | ConfigCheckListProjects() 49 | RegisterProject() 50 | } 51 | 52 | func (c *commandBuild) run(cmd *cobra.Command, args []string) { 53 | getConfig() // nolint: errcheck 54 | aem.GetConfig() // nolint: errcheck 55 | 56 | if c.versionOnly { 57 | err := aem.SetBuildVersion(c.productionBuild) 58 | if err != nil { 59 | output.Printf(output.NORMAL, "\U0000274C Version failed...") 60 | os.Exit(1) 61 | } 62 | os.Exit(0) 63 | } 64 | 65 | err := aem.BuildProject(c.productionBuild, c.skipTests, c.skipCheckStyle, c.skipFrontend) 66 | if err != nil { 67 | output.Printf(output.NORMAL, "\U0000274C Build failed...") 68 | os.Exit(1) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /internal/commands/stop.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/aem" 5 | "github.com/jlentink/aem/internal/aem/dispatcher" 6 | "github.com/jlentink/aem/internal/output" 7 | "github.com/spf13/cobra" 8 | "os" 9 | ) 10 | 11 | type commandStop struct { 12 | verbose bool 13 | instanceName string 14 | groupName string 15 | } 16 | 17 | func (c *commandStop) setup() *cobra.Command { 18 | cmd := &cobra.Command{ 19 | Use: "stop", 20 | Short: "stop Adobe Experience Manager instance", 21 | PreRun: c.preRun, 22 | Run: c.run, 23 | } 24 | cmd.Flags().StringVarP(&c.instanceName, "name", "n", aem.GetDefaultInstanceName(), "Instance to stop") 25 | cmd.Flags().StringVarP(&c.groupName, "group", "g", "", "Instance to start") 26 | return cmd 27 | } 28 | 29 | func (c *commandStop) preRun(cmd *cobra.Command, args []string) { 30 | c.verbose, _ = cmd.Flags().GetBool("verbose") 31 | output.SetVerbose(verbose) 32 | 33 | ConfigCheckListProjects() 34 | RegisterProject() 35 | } 36 | 37 | func (c *commandStop) run(cmd *cobra.Command, args []string) { 38 | 39 | cnf, instances, errorString, err := getConfigAndInstanceOrGroupWithRoles(c.instanceName, c.groupName, []string{aem.RoleAuthor, aem.RolePublisher, aem.RoleDispatcher}) 40 | if err != nil { 41 | output.Printf(output.NORMAL, errorString, err.Error()) 42 | os.Exit(ExitError) 43 | } 44 | 45 | for _, currentInstance := range instances { 46 | if currentInstance.InstanceOf([]string{aem.RoleAuthor, aem.RolePublisher}) { 47 | err = aem.Stop(currentInstance) 48 | if err != nil { 49 | output.Printf(output.NORMAL, "Could not stop instance. (%s)", err.Error()) 50 | os.Exit(ExitError) 51 | } 52 | } else if currentInstance.InstanceOf([]string{aem.RoleDispatcher}){ 53 | err := dispatcher.Stop(currentInstance, cnf) 54 | if err != nil { 55 | output.Printf(output.NORMAL, "Could not stop instance. (%s)", err.Error()) 56 | os.Exit(ExitError) 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /internal/commands/cdnCredentials.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "github.com/jlentink/aem/internal/aem/objects" 7 | "github.com/jlentink/aem/internal/output" 8 | "github.com/spf13/cobra" 9 | "os" 10 | "strings" 11 | ) 12 | 13 | type commandCdnCredentials struct { 14 | verbose bool 15 | cmd *cobra.Command 16 | name string 17 | url string 18 | apiKey string 19 | soft bool 20 | } 21 | 22 | func (c *commandCdnCredentials) setup() *cobra.Command { 23 | c.cmd = &cobra.Command{ 24 | Use: "credentials", 25 | Aliases: []string{"passwords"}, 26 | Short: "Store credentials for CDN's", 27 | PreRun: c.preRun, 28 | Run: c.run, 29 | } 30 | 31 | c.cmd.Flags().StringVarP(&c.name, "name", "n", "", "CDN name") 32 | return c.cmd 33 | } 34 | 35 | func (c *commandCdnCredentials) preRun(cmd *cobra.Command, args []string) { 36 | c.verbose, _ = cmd.Flags().GetBool("verbose") 37 | output.SetVerbose(verbose) 38 | 39 | ConfigCheckListProjects() 40 | RegisterProject() 41 | } 42 | 43 | func (c *commandCdnCredentials) run(cmd *cobra.Command, args []string) { 44 | if c.name != "" { 45 | cdn, err := getCdnConfig(c.name) 46 | if err != nil { 47 | output.Printf(output.NORMAL, "%s", err.Error()) 48 | os.Exit(ExitError) 49 | } 50 | c.setCredentials(cdn) 51 | } else { 52 | cnf, err := getConfig() 53 | if err != nil { 54 | output.Printf(output.NORMAL, "%s", err.Error()) 55 | os.Exit(ExitError) 56 | } 57 | for _, cdn := range cnf.CDNs { 58 | c.setCredentials(&cdn) 59 | } 60 | } 61 | } 62 | 63 | func (c *commandCdnCredentials) setCredentials(cdn *objects.CDN){ 64 | if strings.EqualFold(cdn.CdnType, "fastly") { 65 | reader := bufio.NewReader(os.Stdin) 66 | fmt.Print("Enter Fastly API key: ") 67 | pw, _ := reader.ReadString('\n') 68 | err := cdn.SetAPIKey(pw[:len(pw)-1]) 69 | if err != nil { 70 | output.Printf(output.NORMAL, "Could not set/update password: %s", err.Error()) 71 | os.Exit(ExitError) 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /internal/commands/oakCheck.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/aem" 5 | "github.com/jlentink/aem/internal/cli/oak" 6 | "github.com/jlentink/aem/internal/cli/project" 7 | "github.com/jlentink/aem/internal/output" 8 | "github.com/spf13/cobra" 9 | "os" 10 | ) 11 | 12 | type commandOakCheck struct { 13 | verbose bool 14 | instanceName string 15 | aemVersion string 16 | oakVersion string 17 | } 18 | 19 | func (c *commandOakCheck) setup() *cobra.Command { 20 | cmd := &cobra.Command{ 21 | Use: "check", 22 | Aliases: []string{}, 23 | Short: "Run oak check", 24 | PreRun: c.preRun, 25 | Run: c.run, 26 | } 27 | cmd.Flags().StringVarP(&c.instanceName, "name", "n", aem.GetDefaultInstanceName(), "Instance to stop") 28 | cmd.Flags().StringVarP(&c.aemVersion, "aem", "a", ``, "Version of AEM to use oak-run on. (use matching AEM version of oak-run)") 29 | cmd.Flags().StringVarP(&c.oakVersion, "oak", "o", ``, "Define version of oak-run to use") 30 | cmd.MarkFlagRequired("name") // nolint: errcheck 31 | return cmd 32 | } 33 | 34 | func (c *commandOakCheck) preRun(cmd *cobra.Command, args []string) { 35 | c.verbose, _ = cmd.Flags().GetBool("verbose") 36 | output.SetVerbose(verbose) 37 | 38 | ConfigCheckListProjects() 39 | RegisterProject() 40 | } 41 | 42 | func (c *commandOakCheck) run(cmd *cobra.Command, args []string) { 43 | _, i, errorString, err := getConfigAndInstance(c.instanceName) 44 | if err != nil { 45 | output.Printf(output.NORMAL, errorString, err.Error()) 46 | os.Exit(ExitError) 47 | } 48 | 49 | instancePath, err := project.GetRunDirLocation(*i) 50 | if err != nil { 51 | output.Printf(output.NORMAL, errorString, err.Error()) 52 | os.Exit(ExitError) 53 | } 54 | 55 | oak.SetDefaultVersion(aem.Cnf.OakVersion) 56 | path, _ := oak.Get(i.GetVersion(), aem.Cnf.OakVersion) 57 | oakArgs := []string{"check", instancePath + oak.RepoPath} 58 | oak.Execute(path, aem.Cnf.OakOptions, oakArgs) // nolint: errcheck 59 | 60 | } 61 | -------------------------------------------------------------------------------- /internal/commands/oakCompact.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/aem" 5 | "github.com/jlentink/aem/internal/cli/oak" 6 | "github.com/jlentink/aem/internal/cli/project" 7 | "github.com/jlentink/aem/internal/output" 8 | "github.com/spf13/cobra" 9 | "os" 10 | ) 11 | 12 | type commandOakCompact struct { 13 | verbose bool 14 | instanceName string 15 | aemVersion string 16 | oakVersion string 17 | } 18 | 19 | func (c *commandOakCompact) setup() *cobra.Command { 20 | cmd := &cobra.Command{ 21 | Use: "compact", 22 | Aliases: []string{}, 23 | Short: "Run oak compact", 24 | PreRun: c.preRun, 25 | Run: c.run, 26 | } 27 | cmd.Flags().StringVarP(&c.instanceName, "name", "n", aem.GetDefaultInstanceName(), "Instance to stop") 28 | cmd.Flags().StringVarP(&c.aemVersion, "aem", "a", ``, "Version of AEM to use oak-run on. (use matching AEM version of oak-run)") 29 | cmd.Flags().StringVarP(&c.oakVersion, "oak", "o", ``, "Define version of oak-run to use") 30 | cmd.MarkFlagRequired("name") // nolint: errcheck 31 | return cmd 32 | } 33 | 34 | func (c *commandOakCompact) preRun(cmd *cobra.Command, args []string) { 35 | c.verbose, _ = cmd.Flags().GetBool("verbose") 36 | output.SetVerbose(verbose) 37 | 38 | ConfigCheckListProjects() 39 | RegisterProject() 40 | } 41 | 42 | func (c *commandOakCompact) run(cmd *cobra.Command, args []string) { 43 | _, i, errorString, err := getConfigAndInstance(c.instanceName) 44 | if err != nil { 45 | output.Printf(output.NORMAL, errorString, err.Error()) 46 | os.Exit(ExitError) 47 | } 48 | 49 | instancePath, err := project.GetRunDirLocation(*i) 50 | if err != nil { 51 | output.Printf(output.NORMAL, errorString, err.Error()) 52 | os.Exit(ExitError) 53 | } 54 | 55 | oak.SetDefaultVersion(aem.Cnf.OakVersion) 56 | path, _ := oak.Get(i.GetVersion(), aem.Cnf.OakVersion) 57 | oakArgs := []string{"compact", instancePath + oak.RepoPath} 58 | oak.Execute(path, aem.Cnf.OakOptions, oakArgs) // nolint: errcheck 59 | 60 | } 61 | -------------------------------------------------------------------------------- /internal/commands/oakExplore.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/aem" 5 | "github.com/jlentink/aem/internal/cli/oak" 6 | "github.com/jlentink/aem/internal/cli/project" 7 | "github.com/jlentink/aem/internal/output" 8 | "github.com/spf13/cobra" 9 | "os" 10 | ) 11 | 12 | type commandOakExplore struct { 13 | verbose bool 14 | instanceName string 15 | aemVersion string 16 | oakVersion string 17 | } 18 | 19 | func (c *commandOakExplore) setup() *cobra.Command { 20 | cmd := &cobra.Command{ 21 | Use: "explorer", 22 | Aliases: []string{}, 23 | Short: "Run oak explorer", 24 | PreRun: c.preRun, 25 | Run: c.run, 26 | } 27 | cmd.Flags().StringVarP(&c.instanceName, "name", "n", aem.GetDefaultInstanceName(), "Instance to stop") 28 | cmd.Flags().StringVarP(&c.aemVersion, "aem", "a", ``, "Version of AEM to use oak-run on. (use matching AEM version of oak-run)") 29 | cmd.Flags().StringVarP(&c.oakVersion, "oak", "o", ``, "Define version of oak-run to use") 30 | cmd.MarkFlagRequired("name") // nolint: errcheck 31 | return cmd 32 | } 33 | 34 | func (c *commandOakExplore) preRun(cmd *cobra.Command, args []string) { 35 | c.verbose, _ = cmd.Flags().GetBool("verbose") 36 | output.SetVerbose(verbose) 37 | 38 | ConfigCheckListProjects() 39 | RegisterProject() 40 | } 41 | 42 | func (c *commandOakExplore) run(cmd *cobra.Command, args []string) { 43 | _, i, errorString, err := getConfigAndInstance(c.instanceName) 44 | if err != nil { 45 | output.Printf(output.NORMAL, errorString, err.Error()) 46 | os.Exit(ExitError) 47 | } 48 | 49 | instancePath, err := project.GetRunDirLocation(*i) 50 | if err != nil { 51 | output.Printf(output.NORMAL, errorString, err.Error()) 52 | os.Exit(ExitError) 53 | } 54 | 55 | oak.SetDefaultVersion(aem.Cnf.OakVersion) 56 | path, _ := oak.Get(i.GetVersion(), aem.Cnf.OakVersion) 57 | oakArgs := []string{"explore", instancePath + oak.RepoPath} 58 | oak.Execute(path, aem.Cnf.OakOptions, oakArgs) // nolint: errcheck 59 | 60 | } 61 | -------------------------------------------------------------------------------- /internal/commands/generatedump.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gobuffalo/packr/v2" 6 | "github.com/jlentink/aem/internal/output" 7 | "github.com/spf13/cobra" 8 | "io/ioutil" 9 | "os" 10 | "path" 11 | ) 12 | 13 | type commandGenerateDump struct { 14 | verbose bool 15 | } 16 | 17 | func (c *commandGenerateDump) setup() *cobra.Command { 18 | cmd := &cobra.Command{ 19 | Use: "init", 20 | Short: "Write default templates to disk", 21 | Aliases: []string{}, 22 | PreRun: c.preRun, 23 | Run: c.run, 24 | } 25 | 26 | return cmd 27 | } 28 | 29 | func (c *commandGenerateDump) preRun(cmd *cobra.Command, args []string) { 30 | c.verbose, _ = cmd.Flags().GetBool("verbose") 31 | output.SetVerbose(verbose) 32 | 33 | ConfigCheckListProjects() 34 | RegisterProject() 35 | } 36 | 37 | func (c *commandGenerateDump) run(cmd *cobra.Command, args []string) { 38 | if _, err := os.Stat("templates"); !os.IsNotExist(err) { 39 | output.Printf(output.NORMAL, "Found a template dir. No need to run. exiting.") 40 | os.Exit(ExitNormal) 41 | } 42 | 43 | box := packr.New("templates", "../../_templates/") 44 | files := box.List() 45 | 46 | for _, entry := range files { 47 | f := path.Base(entry) 48 | d := "templates/" + path.Dir(entry) 49 | c, err := box.Find(entry) 50 | if err != nil { 51 | output.Printf(output.NORMAL, "Found a template dir. No need to run. exiting.") 52 | os.Exit(ExitNormal) 53 | } 54 | err = os.MkdirAll(d, 0777) 55 | if err != nil { 56 | output.Printf(output.NORMAL, "Error while writing directory to disk. %s\n", err.Error()) 57 | os.Exit(ExitNormal) 58 | } 59 | 60 | err = ioutil.WriteFile(d+"/"+f, c, 0666) 61 | if err != nil { 62 | output.Printf(output.NORMAL, "Error while writing file to disk. %s\n", err.Error()) 63 | os.Exit(ExitNormal) 64 | } 65 | output.Printf(output.VERBOSE, "Creating new template file: %s\n", entry) 66 | fmt.Print(".") 67 | } 68 | fmt.Print("\n Files written to disk. You can now edit or start generating code.\n") 69 | } 70 | -------------------------------------------------------------------------------- /internal/sliceutil/Inslice_test.go: -------------------------------------------------------------------------------- 1 | package sliceutil 2 | 3 | import "testing" 4 | 5 | func TestInSliceInt64(t *testing.T) { 6 | type args struct { 7 | slice []int64 8 | needle int64 9 | } 10 | tests := []struct { 11 | name string 12 | args args 13 | want bool 14 | }{ 15 | { 16 | name: "Find 1 in slice", 17 | args: args{ 18 | slice: []int64{1, 2, 3, 4, 5, 6, 7, 8}, 19 | needle: 1, 20 | }, 21 | want: true, 22 | }, 23 | { 24 | name: "Find 11 in slice", 25 | args: args{ 26 | slice: []int64{1, 2, 3, 4, 5, 6, 7, 8}, 27 | needle: 11, 28 | }, 29 | want: false, 30 | }, 31 | { 32 | name: "Find 9 in slice", 33 | args: args{ 34 | slice: []int64{1, 2, 3, 4, 5, 6, 7, 8}, 35 | needle: 9, 36 | }, 37 | want: false, 38 | }, 39 | } 40 | for _, tt := range tests { 41 | t.Run(tt.name, func(t *testing.T) { 42 | if got := InSliceInt64(tt.args.slice, tt.args.needle); got != tt.want { 43 | t.Errorf("InSliceInt64() = %v, want %v", got, tt.want) 44 | } 45 | }) 46 | } 47 | } 48 | 49 | func TestInSliceString(t *testing.T) { 50 | type args struct { 51 | slice []string 52 | needle string 53 | } 54 | tests := []struct { 55 | name string 56 | args args 57 | want bool 58 | }{ 59 | { 60 | name: "Find a in slice", 61 | args: args{ 62 | slice: []string{"a", "aa", "bbb", "ccc"}, 63 | needle: "a", 64 | }, 65 | want: true, 66 | }, 67 | { 68 | name: "Find aa in slice", 69 | args: args{ 70 | slice: []string{"a", "aa", "bbb", "ccc"}, 71 | needle: "aa", 72 | }, 73 | want: true, 74 | }, 75 | { 76 | name: "Find aaa in slice", 77 | args: args{ 78 | slice: []string{"a", "aa", "bbb", "ccc"}, 79 | needle: "aaa", 80 | }, 81 | want: false, 82 | }, 83 | } 84 | for _, tt := range tests { 85 | t.Run(tt.name, func(t *testing.T) { 86 | if got := InSliceString(tt.args.slice, tt.args.needle); got != tt.want { 87 | t.Errorf("InSliceString() = %v, want %v", got, tt.want) 88 | } 89 | }) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /internal/commands/oakCheckpoints.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/aem" 5 | "github.com/jlentink/aem/internal/cli/oak" 6 | "github.com/jlentink/aem/internal/cli/project" 7 | "github.com/jlentink/aem/internal/output" 8 | "github.com/spf13/cobra" 9 | "os" 10 | ) 11 | 12 | type commandOakCheckpoints struct { 13 | verbose bool 14 | instanceName string 15 | aemVersion string 16 | oakVersion string 17 | } 18 | 19 | func (c *commandOakCheckpoints) setup() *cobra.Command { 20 | cmd := &cobra.Command{ 21 | Use: "checkpoints", 22 | Aliases: []string{}, 23 | Short: "Run oak checkpoints", 24 | PreRun: c.preRun, 25 | Run: c.run, 26 | } 27 | cmd.Flags().StringVarP(&c.instanceName, "name", "n", aem.GetDefaultInstanceName(), "Instance to stop") 28 | cmd.Flags().StringVarP(&c.aemVersion, "aem", "a", ``, "Version of AEM to use oak-run on. (use matching AEM version of oak-run)") 29 | cmd.Flags().StringVarP(&c.oakVersion, "oak", "o", ``, "Define version of oak-run to use") 30 | cmd.MarkFlagRequired("name") // nolint: errcheck 31 | return cmd 32 | } 33 | 34 | func (c *commandOakCheckpoints) preRun(cmd *cobra.Command, args []string) { 35 | c.verbose, _ = cmd.Flags().GetBool("verbose") 36 | output.SetVerbose(verbose) 37 | 38 | ConfigCheckListProjects() 39 | RegisterProject() 40 | } 41 | 42 | func (c *commandOakCheckpoints) run(cmd *cobra.Command, args []string) { 43 | _, i, errorString, err := getConfigAndInstance(c.instanceName) 44 | if err != nil { 45 | output.Printf(output.NORMAL, errorString, err.Error()) 46 | os.Exit(ExitError) 47 | } 48 | 49 | instancePath, err := project.GetRunDirLocation(*i) 50 | if err != nil { 51 | output.Printf(output.NORMAL, errorString, err.Error()) 52 | os.Exit(ExitError) 53 | } 54 | 55 | oak.SetDefaultVersion(aem.Cnf.OakVersion) 56 | path, _ := oak.Get(i.GetVersion(), aem.Cnf.OakVersion) 57 | oakArgs := []string{"checkpoints", instancePath + oak.RepoPath} 58 | oak.Execute(path, aem.Cnf.OakOptions, oakArgs) // nolint: errcheck 59 | 60 | } 61 | -------------------------------------------------------------------------------- /internal/aem/stop.go: -------------------------------------------------------------------------------- 1 | package aem 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jlentink/aem/internal/aem/objects" 6 | "github.com/jlentink/aem/internal/cli/project" 7 | "io/ioutil" 8 | "os" 9 | "os/exec" 10 | "runtime" 11 | "strings" 12 | "time" 13 | ) 14 | 15 | func stopWindows(pid, runDir string) error { 16 | cmd := exec.Command("taskkill", "/PID", string(pid), "/f") 17 | cmd.Dir = runDir 18 | cmd.Stdout = os.Stdout 19 | cmd.Stderr = os.Stderr 20 | return cmd.Run() 21 | } 22 | 23 | func stopNix(pid, runDir string) error { 24 | cmd := exec.Command("kill", string(pid)) 25 | cmd.Dir = runDir 26 | cmd.Stdout = os.Stdout 27 | cmd.Stderr = os.Stderr 28 | return cmd.Run() 29 | } 30 | 31 | // Signal sends signal to pid 32 | func Signal(pid, runDir string) error { 33 | cmd := exec.Command("kill", "-0", string(pid)) 34 | cmd.Dir = runDir 35 | return cmd.Run() 36 | } 37 | 38 | func readPid(pidFile string) (string, error) { 39 | pid, err := ioutil.ReadFile(pidFile) 40 | return string(pid), err 41 | } 42 | 43 | // Stop an instance 44 | func Stop(i objects.Instance) error { 45 | pidPath, _ := project.GetPidFileLocation(i) 46 | runPath, _ := project.GetRunDirLocation(i) 47 | if !project.Exists(pidPath) { 48 | return fmt.Errorf("could not find pidfile for : %s", i.Name) 49 | } 50 | 51 | pid, err := readPid(pidPath) 52 | if err != nil { 53 | return fmt.Errorf("could not find pidfile for : %s", err.Error()) 54 | } 55 | 56 | if strings.ToLower(runtime.GOOS) == "windows" { 57 | err = stopWindows(pid, runPath) 58 | if err != nil { 59 | return err 60 | } 61 | return project.Remove(pidPath) 62 | } 63 | 64 | err = stopNix(pid, runPath) 65 | if err != nil { 66 | return err 67 | } 68 | fmt.Printf("Waiting for aem to stop (pid: %s)", pid) 69 | retry := 0 70 | for retry <= 180 { 71 | err = Signal(pid, runPath) 72 | if err != nil { 73 | fmt.Print("\n") 74 | return project.Remove(pidPath) 75 | } 76 | fmt.Print(".") 77 | time.Sleep(1 * time.Second) 78 | retry++ 79 | } 80 | fmt.Print("\n") 81 | return err 82 | } 83 | -------------------------------------------------------------------------------- /internal/casetypes/types.go: -------------------------------------------------------------------------------- 1 | package casetypes 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | ) 7 | 8 | func tokenize(input string) []string { 9 | return strings.Split(input, ` `) 10 | } 11 | 12 | func normalize(input string) string { 13 | re := regexp.MustCompile(`[^a-zA-Z\d]`) 14 | input = re.ReplaceAllString(input, " ") 15 | return strings.TrimSpace(input) 16 | } 17 | 18 | // LowerCase transform all characters to lower case. Alias of strings.ToLower 19 | func LowerCase(input string) string { 20 | return strings.ToLower(input) 21 | } 22 | 23 | // UpperCase transform all characters to upper case. Alias of strings.UpperCase 24 | func UpperCase(input string) string { 25 | return strings.ToUpper(input) 26 | } 27 | 28 | // CamelCase transform string to camel case (camelCase) 29 | func CamelCase(input string) string { 30 | output := "" 31 | for i, val := range tokenize(normalize(input)) { 32 | tok := LowerCase(val) 33 | if i > 0 { 34 | tok = strings.Title(tok) 35 | } 36 | output += tok 37 | } 38 | return output 39 | } 40 | 41 | // PascalCase transform string to pascal case (PascalCase) 42 | func PascalCase(input string) string { 43 | output := "" 44 | for _, val := range tokenize(normalize(input)) { 45 | tok := LowerCase(val) 46 | tok = strings.Title(tok) 47 | output += tok 48 | } 49 | return output 50 | } 51 | 52 | // KababCase transform string to kabab case (kabab-case) 53 | func KababCase(input string) string { 54 | input = LowerCase(normalize(input)) 55 | input = strings.ReplaceAll(input, " ", "-") 56 | return input 57 | } 58 | 59 | // SnakeCase transform string to snake case (slug_case) 60 | func SnakeCase(input string) string { 61 | input = LowerCase(normalize(input)) 62 | re := regexp.MustCompile(`[^a-zA-Z\d]`) 63 | return re.ReplaceAllString(input, "_") 64 | } 65 | 66 | // TitleCase returns a copy of the string s with all Unicode letters that begin words 67 | // mapped to their title case. 68 | func TitleCase(input string) string { 69 | input = LowerCase(input) 70 | return UpperCase(input[0:1]) + input[1:] 71 | } 72 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/jlentink/aem 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/AlecAivazis/survey/v2 v2.0.4 7 | github.com/BurntSushi/toml v0.3.1 8 | github.com/alecthomas/gometalinter v3.0.0+incompatible // indirect 9 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 // indirect 10 | github.com/antchfx/xmlquery v1.3.1 11 | github.com/danieljoos/wincred v1.0.2 // indirect 12 | github.com/daviddengcn/go-colortext v0.0.0-20180409174941-186a3d44e920 13 | github.com/dustin/go-humanize v1.0.0 14 | github.com/fastly/go-fastly/v3 v3.9.3 15 | github.com/go-git/go-git/v5 v5.1.0 16 | github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a 17 | github.com/go-openapi/strfmt v0.19.2 // indirect 18 | github.com/gobuffalo/packr/v2 v2.8.0 19 | github.com/godbus/dbus v4.1.0+incompatible // indirect 20 | github.com/golangplus/testing v1.0.0 // indirect 21 | github.com/gordonklaus/ineffassign v0.0.0-20190601041439-ed7b1b5ee0f8 // indirect 22 | github.com/hpcloud/tail v1.0.0 23 | github.com/jedib0t/go-pretty v4.2.1+incompatible 24 | github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 25 | github.com/karrick/godirwalk v1.15.6 // indirect 26 | github.com/manifoldco/promptui v0.3.2 27 | github.com/mattn/go-runewidth v0.0.4 // indirect 28 | github.com/nicksnyder/go-i18n v1.10.1 // indirect 29 | github.com/schollz/progressbar/v2 v2.13.2 30 | github.com/sirupsen/logrus v1.5.0 // indirect 31 | github.com/spf13/afero v1.1.2 32 | github.com/spf13/cobra v0.0.7 33 | github.com/spf13/pflag v1.0.5 // indirect 34 | github.com/tidwall/gjson v1.9.3 35 | github.com/zalando/go-keyring v0.0.0-20190531073407-f65c47520c89 36 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422 // indirect 37 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a // indirect 38 | golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa // indirect 39 | gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20180810215634-df19058c872c // indirect 40 | gopkg.in/fsnotify.v1 v1.4.7 // indirect 41 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect 42 | ) 43 | -------------------------------------------------------------------------------- /internal/aem/Find.go: -------------------------------------------------------------------------------- 1 | package aem 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jlentink/aem/internal/aem/objects" 6 | "github.com/jlentink/aem/internal/sliceutil" 7 | ) 8 | 9 | // Roles available for selection 10 | const ( 11 | RoleAuthor = "author" 12 | RoleDispatcher = "dispatch" 13 | RolePublisher = "publish" 14 | ) 15 | 16 | // GetByName find instance by name 17 | func GetByName(n string, i []objects.Instance) (*objects.Instance, error) { 18 | for _, instance := range i { 19 | instance := instance 20 | if n == instance.Name { 21 | return &instance, nil 22 | } 23 | if len(instance.Aliases) > 0 { 24 | for _, alias := range instance.Aliases { 25 | if n == alias { 26 | return &instance, nil 27 | } 28 | } 29 | } 30 | } 31 | return nil, fmt.Errorf("instance %s is not defined", n) 32 | } 33 | 34 | // GetByGroup find instances by group 35 | func GetByGroup(g string, i []objects.Instance) ([]objects.Instance, error) { 36 | instances := make([]objects.Instance, 0) 37 | for _, instance := range i { 38 | if g == instance.Group { 39 | instances = append(instances, instance) 40 | } 41 | } 42 | 43 | err := fmt.Errorf("could not find instance in group. (%s)", g) 44 | if len(instances) > 0 { 45 | err = nil 46 | } 47 | 48 | return instances, err 49 | } 50 | 51 | // GetByGroupAndRole find instances by group and role 52 | func GetByGroupAndRole(g string, i []objects.Instance, r string) ([]objects.Instance, error) { 53 | return GetByGroupAndRoles(g, i, []string{r}) 54 | } 55 | 56 | // GetByGroupAndRoles find instances by group and roles 57 | func GetByGroupAndRoles(g string, i []objects.Instance, r []string) ([]objects.Instance, error) { 58 | instances := make([]objects.Instance, 0) 59 | for _, instance := range i { 60 | if g == instance.Group && sliceutil.InSliceString(r, instance.Type) { 61 | instances = append(instances, instance) 62 | } 63 | } 64 | 65 | err := fmt.Errorf("could not find instance in group. (%s)", g) 66 | if len(instances) > 0 { 67 | err = nil 68 | } 69 | 70 | return instances, err 71 | } 72 | -------------------------------------------------------------------------------- /internal/commands/cloudUpgrade.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/aem" 5 | "github.com/jlentink/aem/internal/aem/bundle" 6 | "github.com/jlentink/aem/internal/cli/project" 7 | "github.com/jlentink/aem/internal/output" 8 | "github.com/spf13/cobra" 9 | "os" 10 | ) 11 | 12 | type cloudUpgrade struct { 13 | verbose bool 14 | instanceName string 15 | instanceGroup string 16 | bundle string 17 | startLevel string 18 | } 19 | 20 | func (c *cloudUpgrade) setup() *cobra.Command { 21 | cmd := &cobra.Command{ 22 | Use: "install", 23 | Short: "Install bundle", 24 | PreRun: c.preRun, 25 | Run: c.run, 26 | } 27 | cmd.Flags().StringVarP(&c.instanceName, "name", "n", aem.GetDefaultInstanceName(), "Instance to install bundle on") 28 | //cmd.Flags().StringVarP(&c.instanceGroup, "group", "g", ``, "Instance group to install bundle on") 29 | //cmd.Flags().StringVarP(&c.bundle, "bundle", "b", ``, "Instance group to install bundle on") 30 | //cmd.Flags().StringVarP(&c.startLevel, "level", "l", "20", "Bundle start level") 31 | return cmd 32 | } 33 | 34 | func (c *cloudUpgrade) preRun(cmd *cobra.Command, args []string) { 35 | c.verbose, _ = cmd.Flags().GetBool("verbose") 36 | output.SetVerbose(c.verbose) 37 | 38 | ConfigCheckListProjects() 39 | RegisterProject() 40 | } 41 | 42 | func (c *cloudUpgrade) run(cmd *cobra.Command, args []string) { 43 | _, is, errorString, err := getConfigAndInstanceOrGroupWithRoles(c.instanceName, c.instanceGroup, []string{aem.RoleAuthor, aem.RolePublisher}) 44 | if err != nil { 45 | output.Printf(output.NORMAL, errorString, err.Error()) 46 | os.Exit(ExitError) 47 | } 48 | 49 | if !project.Exists(c.bundle) { 50 | output.Printf(output.NORMAL, "Could not find bundle at: %s", c.bundle) 51 | os.Exit(ExitError) 52 | } 53 | 54 | for _, i := range is { 55 | i := i 56 | err := bundle.Install(&i, c.bundle, c.startLevel) 57 | if err != nil { 58 | output.Printf(output.NORMAL, "Could not install bundle %s", err.Error()) 59 | os.Exit(ExitError) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /internal/commands/bundleInstall.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/aem" 5 | "github.com/jlentink/aem/internal/aem/bundle" 6 | "github.com/jlentink/aem/internal/cli/project" 7 | "github.com/jlentink/aem/internal/output" 8 | "github.com/spf13/cobra" 9 | "os" 10 | ) 11 | 12 | type commandBundleInstall struct { 13 | verbose bool 14 | instanceName string 15 | instanceGroup string 16 | bundle string 17 | startLevel string 18 | } 19 | 20 | func (c *commandBundleInstall) setup() *cobra.Command { 21 | cmd := &cobra.Command{ 22 | Use: "install", 23 | Short: "Install bundle", 24 | PreRun: c.preRun, 25 | Run: c.run, 26 | } 27 | cmd.Flags().StringVarP(&c.instanceName, "name", "n", aem.GetDefaultInstanceName(), "Instance to install bundle on") 28 | cmd.Flags().StringVarP(&c.instanceGroup, "group", "g", ``, "Instance group to install bundle on") 29 | cmd.Flags().StringVarP(&c.bundle, "bundle", "b", ``, "Path to the bundle.") 30 | cmd.Flags().StringVarP(&c.startLevel, "level", "l", "20", "Bundle start level") 31 | return cmd 32 | } 33 | 34 | func (c *commandBundleInstall) preRun(cmd *cobra.Command, args []string) { 35 | c.verbose, _ = cmd.Flags().GetBool("verbose") 36 | output.SetVerbose(c.verbose) 37 | 38 | ConfigCheckListProjects() 39 | RegisterProject() 40 | } 41 | 42 | func (c *commandBundleInstall) run(cmd *cobra.Command, args []string) { 43 | _, is, errorString, err := getConfigAndInstanceOrGroupWithRoles(c.instanceName, c.instanceGroup, []string{aem.RoleAuthor, aem.RolePublisher}) 44 | if err != nil { 45 | output.Printf(output.NORMAL, errorString, err.Error()) 46 | os.Exit(ExitError) 47 | } 48 | 49 | if !project.Exists(c.bundle) { 50 | output.Printf(output.NORMAL, "Could not find bundle at: %s", c.bundle) 51 | os.Exit(ExitError) 52 | } 53 | 54 | for _, i := range is { 55 | i := i 56 | err := bundle.Install(&i, c.bundle, c.startLevel) 57 | if err != nil { 58 | output.Printf(output.NORMAL, "Could not install bundle %s", err.Error()) 59 | os.Exit(ExitError) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /internal/cli/project/write_test.go: -------------------------------------------------------------------------------- 1 | package project 2 | 3 | import "testing" 4 | 5 | func TestWriteTextFile(t *testing.T) { 6 | type args struct { 7 | path string 8 | content string 9 | } 10 | tests := []struct { 11 | name string 12 | args args 13 | want bool 14 | wantFile bool 15 | wantRo bool 16 | wantErr bool 17 | }{ 18 | { 19 | name: "Write file", 20 | wantFile: true, 21 | wantRo: false, 22 | wantErr: false, 23 | }, 24 | { 25 | name: "Get RO error", 26 | want: true, 27 | wantFile: true, 28 | wantRo: true, 29 | wantErr: true, 30 | }, 31 | } 32 | for _, tt := range tests { 33 | t.Run(tt.name, func(t *testing.T) { 34 | Mock(tt.wantRo) 35 | _, err := WriteTextFile(tt.args.path, tt.args.content) 36 | 37 | if tt.wantErr && err == nil { 38 | t.Errorf("WriteTextFile() = %v, want err %v", err, tt.want) 39 | } 40 | 41 | if got := Exists(tt.args.path); got != tt.wantFile { 42 | t.Errorf("WriteTextFile() = %v, want %v", got, tt.want) 43 | } 44 | }) 45 | } 46 | } 47 | 48 | func TestWriteGitIgnoreFile(t *testing.T) { 49 | tests := []struct { 50 | name string 51 | want bool 52 | wantFile bool 53 | wantFSErr bool 54 | wantFSRO bool 55 | }{ 56 | { 57 | name: "Test if ignore exists", 58 | want: true, 59 | wantFile: true, 60 | wantFSErr: false, 61 | wantFSRO: false, 62 | }, 63 | { 64 | name: "FS err", 65 | want: true, 66 | wantFile: true, 67 | wantFSErr: true, 68 | wantFSRO: false, 69 | }, 70 | { 71 | name: "RO err", 72 | want: false, 73 | wantFile: false, 74 | wantFSErr: false, 75 | wantFSRO: true, 76 | }, 77 | } 78 | for _, tt := range tests { 79 | t.Run(tt.name, func(t *testing.T) { 80 | Mock(tt.wantFSRO) 81 | FilesystemError(tt.wantFSErr) 82 | path, _ := getIgnoreFileLocation() 83 | WriteGitIgnoreFile() 84 | 85 | if got := Exists(path); got != tt.want { 86 | t.Errorf("WriteGitIgnoreFile() = %v, want %v", got, tt.want) 87 | } 88 | }) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /internal/aem/objects/crx.go: -------------------------------------------------------------------------------- 1 | package objects 2 | 3 | import "encoding/xml" 4 | 5 | //CrxResponse descriptive struct 6 | type CrxResponse struct { 7 | XMLName xml.Name `xml:"crx"` 8 | Text string `xml:",chardata"` 9 | Version string `xml:"version,attr"` 10 | User string `xml:"user,attr"` 11 | Workspace string `xml:"workspace,attr"` 12 | Request struct { 13 | Text string `xml:",chardata"` 14 | Param []struct { 15 | Text string `xml:",chardata"` 16 | Name string `xml:"name,attr"` 17 | Value string `xml:"value,attr"` 18 | } `xml:"param"` 19 | } `xml:"request"` 20 | Response struct { 21 | Text string `xml:",chardata"` 22 | Data struct { 23 | Text string `xml:",chardata"` 24 | Package struct { 25 | Text string `xml:",chardata"` 26 | Group struct { 27 | Text string `xml:",chardata"` 28 | } `xml:"group"` 29 | Name struct { 30 | Text string `xml:",chardata"` 31 | } `xml:"name"` 32 | Version struct { 33 | Text string `xml:",chardata"` 34 | } `xml:"version"` 35 | DownloadName struct { 36 | Text string `xml:",chardata"` 37 | } `xml:"downloadName"` 38 | Size struct { 39 | Text string `xml:",chardata"` 40 | } `xml:"size"` 41 | Created struct { 42 | Text string `xml:",chardata"` 43 | } `xml:"created"` 44 | CreatedBy struct { 45 | Text string `xml:",chardata"` 46 | } `xml:"createdBy"` 47 | LastModified struct { 48 | Text string `xml:",chardata"` 49 | } `xml:"lastModified"` 50 | LastModifiedBy struct { 51 | Text string `xml:",chardata"` 52 | } `xml:"lastModifiedBy"` 53 | LastUnpacked struct { 54 | Text string `xml:",chardata"` 55 | } `xml:"lastUnpacked"` 56 | LastUnpackedBy struct { 57 | Text string `xml:",chardata"` 58 | } `xml:"lastUnpackedBy"` 59 | } `xml:"package"` 60 | Log struct { 61 | Text string `xml:",chardata"` 62 | } `xml:"log"` 63 | } `xml:"data"` 64 | Status struct { 65 | Text string `xml:",chardata"` 66 | Code string `xml:"code,attr"` 67 | } `xml:"status"` 68 | } `xml:"response"` 69 | } 70 | -------------------------------------------------------------------------------- /internal/http/Get.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "fmt" 5 | "github.com/go-http-utils/headers" 6 | "github.com/jlentink/aem/internal/version" 7 | "io/ioutil" 8 | "net/http" 9 | "net/url" 10 | ) 11 | 12 | // GetPlain does a plain get request to an URL 13 | func GetPlain(uri string, username string, password string) ([]byte, error) { 14 | URL, err := url.Parse(uri) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | if username != "" || password != "" { 20 | URL.User = url.UserPassword(username, password) 21 | } 22 | 23 | return Get(URL) 24 | } 25 | 26 | // GetPlainWithHeaders Do a plain request with specific headers 27 | func GetPlainWithHeaders(uri string, username string, password string, header []Header) ([]byte, error) { 28 | URL, err := url.Parse(uri) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | if username != "" || password != "" { 34 | URL.User = url.UserPassword(username, password) 35 | } 36 | 37 | return GetWithHeaders(URL, header) 38 | } 39 | 40 | // GetWithHeaders Do a get request with url.URL 41 | func GetWithHeaders(uri *url.URL, header []Header) ([]byte, error) { 42 | req, err := http.NewRequest(http.MethodGet, URLToURLString(uri), nil) 43 | if err != nil { 44 | return nil, err 45 | } 46 | for _, h := range header { 47 | req.Header.Add(h.Key, h.Value) 48 | } 49 | req.Header.Add(headers.UserAgent, "aemCLI - "+version.GetVersion()) 50 | req.URL = uri 51 | 52 | for _, h := range header { 53 | if h.Key == "Host" { 54 | req.Host = h.Value 55 | } 56 | } 57 | // req.Host = "flush" 58 | 59 | client := &http.Client{} 60 | resp, err := client.Do(req) 61 | if err != nil { 62 | return []byte{}, err 63 | } 64 | defer resp.Body.Close() // nolint: errcheck 65 | 66 | body, err := ioutil.ReadAll(resp.Body) 67 | if err != nil { 68 | return []byte{}, err 69 | } 70 | 71 | if resp.StatusCode >= 400 { 72 | return body, fmt.Errorf("received unexpected HTTP status. (%d)", resp.StatusCode) 73 | } 74 | 75 | return body, nil 76 | } 77 | 78 | // Get Perform a simple get request 79 | func Get(uri *url.URL) ([]byte, error) { 80 | return GetWithHeaders(uri, []Header{{Key: headers.CacheControl, Value: configNoCache}}) 81 | } 82 | -------------------------------------------------------------------------------- /internal/commands/passwords.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "github.com/jlentink/aem/internal/aem" 7 | "github.com/jlentink/aem/internal/output" 8 | "github.com/spf13/cobra" 9 | "os" 10 | ) 11 | 12 | type commandPassword struct { 13 | verbose bool 14 | instanceName string 15 | instanceGroup string 16 | } 17 | 18 | func (c *commandPassword) setup() *cobra.Command { 19 | cmd := &cobra.Command{ 20 | Use: "passwords", 21 | Aliases: []string{"password", "passwd"}, 22 | Short: "Set passwords into your keychain", 23 | PreRun: c.preRun, 24 | Run: c.run, 25 | } 26 | cmd.Flags().StringVarP(&c.instanceName, "name", "n", ``, "Update specific instance") 27 | cmd.Flags().StringVarP(&c.instanceGroup, "group", "g", ``, "Group to install package on") 28 | return cmd 29 | } 30 | 31 | func (c *commandPassword) preRun(cmd *cobra.Command, args []string) { 32 | c.verbose, _ = cmd.Flags().GetBool("verbose") 33 | output.SetVerbose(c.verbose) 34 | 35 | ConfigCheckListProjects() 36 | RegisterProject() 37 | } 38 | 39 | func (c *commandPassword) run(cmd *cobra.Command, args []string) { 40 | cnf, err := getConfig() 41 | if err != nil { 42 | output.Printf(output.NORMAL, "Could not get config file: %s", err.Error()) 43 | os.Exit(ExitError) 44 | } 45 | 46 | if c.instanceName != "" || c.instanceGroup != "" { 47 | _, i, errorString, err := getConfigAndInstanceOrGroupWithRoles(c.instanceName, c.instanceGroup, []string{aem.RoleAuthor, aem.RolePublisher}) 48 | if err != nil { 49 | output.Printf(output.NORMAL, errorString, err.Error()) 50 | os.Exit(ExitError) 51 | } 52 | cnf.Instances = i 53 | } 54 | 55 | if !cnf.KeyRing { 56 | output.Printf(output.NORMAL, "keyring is disabled. use passwords from the aem.toml file.") 57 | os.Exit(ExitError) 58 | } 59 | 60 | for _, i := range cnf.Instances { 61 | output.Printf(output.NORMAL, "\U0001F5A5 Instance: %s\n", i.Name) 62 | reader := bufio.NewReader(os.Stdin) 63 | fmt.Print("Enter password: ") 64 | pw, _ := reader.ReadString('\n') 65 | err := i.SetPassword(pw[:len(pw)-1]) 66 | if err != nil { 67 | output.Printf(output.NORMAL, "Could not update password: %s", err.Error()) 68 | os.Exit(ExitError) 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /docker/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all build start shell test 2 | 3 | CONTAINER_VERSION?=4.3.2 4 | CONTAINER_NAME?=aem-dispatcher 5 | TESTNODE_VERSION?= 6 | 7 | all: clean build 8 | 9 | clean: 10 | -docker rmi `docker images --format '{{.Repository}}:{{.Tag}}' | grep ${CONTAINER_NAME}` 11 | 12 | build: 13 | docker build . --tag ${CONTAINER_NAME}:${CONTAINER_VERSION} 14 | 15 | shell: 16 | docker run -it --rm --entrypoint "/bin/bash" \ 17 | -v /Volumes/codespace/projects/A/Azko/Platform/dispatcher/src/conf:/usr/local/apache2/conf/conf \ 18 | -v /Volumes/codespace/projects/A/Azko/Platform/dispatcher/src/conf.d:/usr/local/apache2/conf/conf.d \ 19 | -v /Volumes/codespace/projects/A/Azko/Platform/dispatcher/src/conf.dispatcher.d:/usr/local/apache2/conf/conf.dispatcher.d \ 20 | -e DISP_ID=dispatcher1euwest1 \ 21 | -e AUTHOR_IP=host.docker.internal \ 22 | -e AUTHOR_PORT=4502 \ 23 | -e AUTHOR_DEFAULT_HOSTNAME=host.docker.internal \ 24 | -e AUTHOR_DOCROOT=/var/www/author \ 25 | -e PUBLISH_IP=host.docker.internal \ 26 | -e PUBLISH_PORT=4503 \ 27 | -e PUBLISH_DEFAULT_HOSTNAME=host.docker.internal \ 28 | -e PUBLISH_BETA_HOSTNAME= \ 29 | -e PUBLISH_DOCROOT=/var/www/html \ 30 | -e LIVECYCLE_IP=127.0.0.1 \ 31 | -e LIVECYCLE_PORT=8080 \ 32 | -e LIVECYCLE_DEFAULT_HOSTNAME=host.docker.internal \ 33 | -e LIVECYCLE_DOCROOT=/var/www/lc \ 34 | -e CRX_FILTER=deny \ 35 | -p 8080:80 \ 36 | --name dispatcher \ 37 | ${CONTAINER_NAME}:${CONTAINER_VERSION} 38 | 39 | #shell: 40 | # docker run -it --rm --entrypoint "/bin/sh" \ 41 | # -v /Volumes/codespace/projects/A/Azko/Platform/dispatcher/src/conf.d:/etc/httpd/conf.d \ 42 | # -v /Volumes/codespace/projects/A/Azko/Platform/dispatcher/src/conf.dispatcher.d:/etc/httpd/conf.dispatcher.d \ 43 | # ${CONTAINER_NAME} 44 | run: 45 | docker run --rm \ 46 | -v /Volumes/codespace/projeenvcts/A/Azko/Platform/dispatcher/src/conf.d:/etc/httpd/conf.d \ 47 | -v /Volumes/codespace/projects/A/Azko/Platform/dispatcher/src/conf.dispatcher.d:/etc/httpd/conf.dispatcher.d \ 48 | ${CONTAINER_NAME} 49 | 50 | tag: 51 | docker tag ${CONTAINER_NAME}:${CONTAINER_VERSION} jlentink/${CONTAINER_NAME}:${CONTAINER_VERSION} 52 | 53 | push: tag 54 | docker push jlentink/${CONTAINER_NAME}:${CONTAINER_VERSION} 55 | -------------------------------------------------------------------------------- /internal/commands/invalidate.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/aem" 5 | "github.com/jlentink/aem/internal/aem/dispatcher" 6 | "github.com/jlentink/aem/internal/aem/objects" 7 | "github.com/jlentink/aem/internal/output" 8 | "github.com/spf13/cobra" 9 | "os" 10 | ) 11 | 12 | type commandInvalidate struct { 13 | verbose bool 14 | instanceName string 15 | instanceGroup string 16 | path string 17 | } 18 | 19 | func (c *commandInvalidate) setup() *cobra.Command { 20 | cmd := &cobra.Command{ 21 | Use: "invalidate", 22 | Short: "Invalidate path's on dispatcher", 23 | Aliases: []string{"flush"}, 24 | PreRun: c.preRun, 25 | Run: c.run, 26 | } 27 | cmd.Flags().StringVarP(&c.instanceName, "name", "n", ``, "Instance to sent invalidate to") 28 | cmd.Flags().StringVarP(&c.instanceGroup, "group", "g", ``, "Instance group to sent invalidate to") 29 | cmd.Flags().StringVarP(&c.path, "path", "p", ``, "Path to flush") 30 | return cmd 31 | } 32 | 33 | func (c *commandInvalidate) preRun(cmd *cobra.Command, args []string) { 34 | c.verbose, _ = cmd.Flags().GetBool("verbose") 35 | output.SetVerbose(c.verbose) 36 | 37 | ConfigCheckListProjects() 38 | RegisterProject() 39 | } 40 | 41 | func (c *commandInvalidate) run(cmd *cobra.Command, args []string) { 42 | var instances []objects.Instance 43 | if len(c.instanceName) == 0 && len(c.instanceGroup) == 0 { 44 | output.Print(output.NORMAL, "Please set group (-g|--group) or instance(-n|--name) to sent invalidate to.\n") 45 | os.Exit(ExitError) 46 | } 47 | if len(c.instanceName) > 0 { 48 | _, i, errorString, err := getConfigAndInstance(c.instanceName) 49 | instances = append(instances, *i) 50 | if err != nil { 51 | output.Printf(output.NORMAL, errorString, err.Error()) 52 | os.Exit(ExitError) 53 | } 54 | } else { 55 | _, is, errorString, err := getConfigAndGroupWithRole(c.instanceGroup, aem.RoleDispatcher) 56 | instances = is 57 | if err != nil { 58 | output.Printf(output.NORMAL, errorString, err.Error()) 59 | os.Exit(ExitError) 60 | } 61 | 62 | } 63 | 64 | if len(c.path) > 0 { 65 | aem.Cnf.InvalidatePaths = []string{c.path} 66 | } 67 | 68 | dispatcher.InvalidateAll(instances, aem.Cnf.InvalidatePaths) 69 | } 70 | -------------------------------------------------------------------------------- /internal/version/version_test.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import "testing" 4 | 5 | func TestGetVersion(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | want string 9 | }{ 10 | { 11 | name: "Test version return", 12 | want: "1.0", 13 | }, 14 | } 15 | for _, tt := range tests { 16 | t.Run(tt.name, func(t *testing.T) { 17 | Main = tt.want 18 | if got := GetVersion(); got != tt.want { 19 | t.Errorf("GetVersion() = %v, want %v", got, tt.want) 20 | } 21 | }) 22 | } 23 | } 24 | 25 | func TestGetBuild(t *testing.T) { 26 | tests := []struct { 27 | name string 28 | want string 29 | }{ 30 | { 31 | name: "Test build return", 32 | want: "1234567890", 33 | }, 34 | } 35 | for _, tt := range tests { 36 | t.Run(tt.name, func(t *testing.T) { 37 | Build = tt.want 38 | if got := GetBuild(); got != tt.want { 39 | t.Errorf("GetBuild() = %v, want %v", got, tt.want) 40 | } 41 | }) 42 | } 43 | } 44 | 45 | func TestDisplayVersion(t *testing.T) { 46 | type args struct { 47 | v bool 48 | m bool 49 | } 50 | tests := []struct { 51 | name string 52 | args args 53 | want string 54 | build string 55 | version string 56 | }{ 57 | { 58 | name: "Get normal version", 59 | args: args{v: false, m: false}, 60 | version: "1.2.3", 61 | build: "1234567890", 62 | want: "AEMcli\nVersion: 1.2.3\n", 63 | }, 64 | { 65 | name: "Get Minimal version", 66 | args: args{v: false, m: true}, 67 | version: "1.2.3", 68 | build: "1234567890", 69 | want: "1.2.3\n", 70 | }, 71 | { 72 | name: "Get Verbose version", 73 | args: args{v: true, m: false}, 74 | version: "1.2.3", 75 | build: "1234567890", 76 | want: "AEMcli (https://github.com/jlentink/aem)\nVersion: 1.2.3\nBuilt: 1234567890\n", 77 | }, 78 | { 79 | name: "Get Verbose & Minal version", 80 | args: args{v: true, m: true}, 81 | version: "1.2.3", 82 | build: "1234567890", 83 | want: "1.2.3\n", 84 | }, 85 | } 86 | for _, tt := range tests { 87 | t.Run(tt.name, func(t *testing.T) { 88 | Build = tt.build 89 | Main = tt.version 90 | if got := DisplayVersion(tt.args.v, tt.args.m); got != tt.want { 91 | t.Errorf("DisplayVersion() = %v, want %v", got, tt.want) 92 | } 93 | }) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /internal/cli/oak/oak.go: -------------------------------------------------------------------------------- 1 | package oak 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jlentink/aem/internal/cli/project" 6 | "github.com/jlentink/aem/internal/http" 7 | "os" 8 | "os/exec" 9 | ) 10 | 11 | const ( 12 | oakRunRepository = "https://repo1.maven.org/maven2/org/apache/jackrabbit/oak-run/%s/oak-run-%s.jar" 13 | // RepoPath Path to repo 14 | RepoPath = "/repository/segmentstore" 15 | oakJar = "oak-run-%s.jar" 16 | ) 17 | 18 | var ( 19 | oakDefaultVersion = "1.8.12" 20 | ) 21 | 22 | // SetDefaultVersion sets the default oak value to be used 23 | func SetDefaultVersion(version string) { 24 | if len(version) > 0 { 25 | oakDefaultVersion = version 26 | } 27 | } 28 | 29 | func versionForAem(version string) string { 30 | switch version { 31 | case "6.0": 32 | return "1.0.42" 33 | case "6.1": 34 | return "1.2.31" 35 | case "6.2": 36 | return "1.4.24" 37 | case "6.3": 38 | return "1.6.16" 39 | case "6.4": 40 | return "1.8.12" 41 | case "6.5": 42 | return "1.8.2" 43 | default: 44 | return oakDefaultVersion 45 | } 46 | } 47 | 48 | // Get download the correct oak version 49 | func Get(aemVersion, version string) (string, error) { 50 | 51 | if len(aemVersion) > 0 { 52 | version = versionForAem(aemVersion) 53 | } 54 | 55 | if len(version) == 0 { 56 | return ``, fmt.Errorf("no oak version selected. Use --aem or --oak to define version") 57 | } 58 | 59 | binPath, err := project.CreateBinDir() 60 | if err != nil { 61 | return ``, err 62 | } 63 | 64 | oakJar := binPath + fmt.Sprintf(oakJar, version) 65 | 66 | if !project.Exists(oakJar) { 67 | fmt.Printf("Downloading oak version %s...\n", version) 68 | _, err = http.DownloadFile(oakJar, fmt.Sprintf(oakRunRepository, version, version), ``, ``, true) 69 | if err != nil { 70 | return ``, err 71 | } 72 | } 73 | return oakJar, nil 74 | } 75 | 76 | // Execute 's command 77 | func Execute(oakPath string, opts, args []string) error { 78 | 79 | path, err := project.GetInstancesDirLocation() 80 | if err != nil { 81 | return err 82 | } 83 | 84 | param := opts 85 | param = append(param, []string{"-jar", oakPath}...) 86 | param = append(param, args...) 87 | cmd := exec.Command("java", param...) 88 | cmd.Dir = path 89 | cmd.Stdout = os.Stdout 90 | cmd.Stdin = os.Stdin 91 | cmd.Stderr = os.Stderr 92 | return cmd.Run() 93 | } 94 | -------------------------------------------------------------------------------- /internal/commands/activationTree.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/aem" 5 | "github.com/jlentink/aem/internal/aem/replication" 6 | "github.com/jlentink/aem/internal/output" 7 | "github.com/spf13/cobra" 8 | "os" 9 | ) 10 | 11 | type commandActivateTree struct { 12 | verbose bool 13 | instanceName string 14 | instanceGroup string 15 | path string 16 | ignoreDeactivate bool 17 | onlyModified bool 18 | } 19 | 20 | func (c *commandActivateTree) setup() *cobra.Command { 21 | cmd := &cobra.Command{ 22 | Use: "tree", 23 | Short: "Activate Tree", 24 | PreRun: c.preRun, 25 | Run: c.run, 26 | } 27 | cmd.Flags().StringVarP(&c.instanceName, "name", "n", aem.GetDefaultInstanceName(), "Instance to (de)activate page on") 28 | cmd.Flags().StringVarP(&c.instanceGroup, "group", "g", ``, "Instance group to (de)activate page on") 29 | cmd.Flags().StringVarP(&c.path, "path", "p", ``, "Path to (de)activate") 30 | cmd.Flags().BoolVarP(&c.ignoreDeactivate, "ignore-deactivated", "d", false, "Ignore Deactivated") 31 | cmd.Flags().BoolVarP(&c.onlyModified, "only-modified", "o", false, "Only Modified") 32 | cmd.MarkFlagRequired("path") // nolint: errcheck 33 | return cmd 34 | } 35 | 36 | func (c *commandActivateTree) preRun(cmd *cobra.Command, args []string) { 37 | c.verbose, _ = cmd.Flags().GetBool("verbose") 38 | output.SetVerbose(c.verbose) 39 | 40 | ConfigCheckListProjects() 41 | RegisterProject() 42 | } 43 | 44 | func (c *commandActivateTree) run(cmd *cobra.Command, args []string) { 45 | _, is, errorString, err := getConfigAndInstanceOrGroupWithRoles(c.instanceName, c.instanceGroup, []string{aem.RoleAuthor}) 46 | if err != nil { 47 | output.Printf(output.NORMAL, errorString, err.Error()) 48 | os.Exit(ExitError) 49 | } 50 | 51 | if len(c.path) == 0 { 52 | output.Printf(output.NORMAL, "no path provided") 53 | os.Exit(ExitError) 54 | } 55 | 56 | for _, i := range is { 57 | i := i 58 | body, err := replication.ActivateTree(&i, c.path, c.ignoreDeactivate, c.onlyModified) 59 | if err != nil { 60 | output.Printf(output.NORMAL, "Could not activate tree: %s", err.Error()) 61 | os.Exit(ExitError) 62 | } 63 | output.Printf(output.NORMAL, "\U00002705 tree activated: %s\n", c.path) 64 | output.Printf(output.VERBOSE, "%s", body) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /internal/commands/open.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jlentink/aem/internal/aem" 6 | "github.com/jlentink/aem/internal/output" 7 | "github.com/spf13/cobra" 8 | "os" 9 | "os/exec" 10 | "runtime" 11 | ) 12 | 13 | type commandOpen struct { 14 | verbose bool 15 | useIP bool 16 | useSSH bool 17 | instanceName string 18 | } 19 | 20 | func (c *commandOpen) setup() *cobra.Command { 21 | cmd := &cobra.Command{ 22 | Use: "open", 23 | Short: "Open URL for Adobe Experience Manager instance in browser", 24 | PreRun: c.preRun, 25 | Run: c.run, 26 | } 27 | cmd.Flags().StringVarP(&c.instanceName, "name", "n", aem.GetDefaultInstanceName(), "Instance to stop") 28 | cmd.Flags().BoolVarP(&c.useIP, "ip", "i", false, "Show Ip instead of hostname") 29 | cmd.Flags().BoolVarP(&c.useSSH, "ssh", "s", false, "Show SSH url") 30 | return cmd 31 | } 32 | 33 | func (c *commandOpen) preRun(cmd *cobra.Command, args []string) { 34 | c.verbose, _ = cmd.Flags().GetBool("verbose") 35 | 36 | output.SetVerbose(verbose) 37 | 38 | ConfigCheckListProjects() 39 | RegisterProject() 40 | } 41 | 42 | func (c *commandOpen) run(cmd *cobra.Command, args []string) { 43 | if len(args) == 1 { 44 | c.instanceName = args[0] 45 | } 46 | _, i, errorString, err := getConfigAndInstance(c.instanceName) 47 | if err != nil { 48 | output.Printf(output.NORMAL, errorString, err.Error()) 49 | os.Exit(ExitError) 50 | } 51 | 52 | if i.Hostname == "" { 53 | c.useIP = false 54 | } 55 | 56 | if i.Type == "dispatch" || c.useSSH { 57 | if i.SSHUsername != "" { 58 | i.Username = i.SSHUsername 59 | } 60 | if c.useIP { 61 | fmt.Printf("use:\n ssh %s@%s\n", i.SSHUsername, i.IP) 62 | } else { 63 | fmt.Printf("use:\n ssh %s@%s\n", i.SSHUsername, i.Hostname) 64 | } 65 | 66 | return 67 | } 68 | 69 | switch runtime.GOOS { 70 | case "windows": 71 | cmd := exec.Command("rundll32", "url.dll,FileProtocolHandler", aem.URLString(i, c.useIP)) 72 | cmd.Start() // nolint: errcheck 73 | case "darwin": 74 | cmd := exec.Command("open", aem.URLString(i, c.useIP)) 75 | cmd.Start() // nolint: errcheck 76 | case "linux": 77 | cmd := exec.Command("xdg-open", aem.URLString(i, c.useIP)) 78 | cmd.Start() // nolint: errcheck 79 | default: 80 | fmt.Printf("unsuported operating systen %s", runtime.GOOS) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /internal/aem/config.go: -------------------------------------------------------------------------------- 1 | package aem 2 | 3 | import ( 4 | "fmt" 5 | "github.com/BurntSushi/toml" 6 | "github.com/gobuffalo/packr/v2" 7 | "github.com/jlentink/aem/internal/aem/objects" 8 | "github.com/jlentink/aem/internal/cli/project" 9 | "github.com/jlentink/aem/internal/output" 10 | "os" 11 | ) 12 | 13 | // Cnf active configuration 14 | var Cnf *objects.Config 15 | 16 | const ( 17 | instanceMainDefault = `local-author` 18 | instanceEnv = `AEM_ME` 19 | ) 20 | 21 | // ConfigExists is there a config available 22 | func ConfigExists() bool { 23 | path, _ := project.GetConfigFileLocation() 24 | return project.Exists(path) 25 | } 26 | 27 | // Render 's the template to a string 28 | func Render() string { 29 | box := packr.New("templates", "../../_templates/") 30 | b, err:= box.Find("aem.toml") 31 | 32 | if err != nil { 33 | return "" 34 | } 35 | 36 | return string(b) 37 | } 38 | 39 | // WriteConfigFile to disk 40 | func WriteConfigFile() (int, error) { 41 | p, err := project.GetConfigFileLocation() 42 | if err != nil { 43 | return 0, err 44 | } 45 | 46 | p = p[0:len(p)-5] + ".example.toml" 47 | return project.WriteTextFile(p, Render()) 48 | } 49 | 50 | // GetConfig Read config page 51 | func GetConfig() (*objects.Config, error) { 52 | p, err := project.GetConfigFileLocation() 53 | if err != nil { 54 | return nil, fmt.Errorf("could not find config file") 55 | } 56 | 57 | cnf := objects.Config{} 58 | _, err = toml.DecodeFile(p, &cnf) 59 | if err != nil { 60 | return nil, fmt.Errorf("could not decode config file: %s", err.Error()) 61 | } 62 | 63 | Cnf = &cnf 64 | objects.Cnf = &cnf 65 | 66 | if cnf.Schema != objects.SchemaVersion { 67 | fmt.Println("Your toml schema does not match the this version.") 68 | fmt.Printf("It should be \"%s\" but it is \"%s\". Please update.\n", objects.SchemaVersion, cnf.Schema) 69 | os.Exit(2) 70 | } 71 | 72 | return &cnf, nil 73 | } 74 | 75 | // GetDefaultInstanceName Instance based on resolution order 76 | func GetDefaultInstanceName() string { 77 | envName := os.Getenv(instanceEnv) 78 | if len(envName) > 0 { 79 | return envName 80 | } 81 | 82 | c, err := GetConfig() 83 | if err != nil { 84 | output.Printf(output.VERBOSE, "Error in config returning default author") 85 | return instanceMainDefault 86 | } 87 | 88 | if len(c.DefaultInstance) > 0 { 89 | return c.DefaultInstance 90 | } 91 | 92 | return instanceMainDefault 93 | } 94 | -------------------------------------------------------------------------------- /internal/aem/utility_test.go: -------------------------------------------------------------------------------- 1 | package aem 2 | 3 | import ( 4 | "os/user" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func Test_getCurrentUser(t *testing.T) { 10 | usr, _ := user.Current() 11 | mock, _ := user.Current() 12 | mock.Uid = "0" 13 | mock.Gid = "0" 14 | 15 | tests := []struct { 16 | name string 17 | want *user.User 18 | wantErr bool 19 | wantMock bool 20 | }{ 21 | { 22 | name: "get User", 23 | want: usr, 24 | wantErr: false, 25 | wantMock: false, 26 | }, 27 | { 28 | name: "get Mock User", 29 | want: mock, 30 | wantErr: false, 31 | wantMock: true, 32 | }, 33 | } 34 | for _, tt := range tests { 35 | t.Run(tt.name, func(t *testing.T) { 36 | if tt.wantMock { 37 | u = mock 38 | } 39 | got, err := getCurrentUser() 40 | if (err != nil) != tt.wantErr { 41 | t.Errorf("getCurrentUser() error = %v, wantErr %v", err, tt.wantErr) 42 | return 43 | } 44 | if !reflect.DeepEqual(got, tt.want) { 45 | t.Errorf("getCurrentUser() = %v, want %v", got, tt.want) 46 | } 47 | 48 | if tt.wantMock && got.Uid != "0" { 49 | t.Errorf("getCurrentUser() = %v, want %v", got, tt.want) 50 | } 51 | }) 52 | } 53 | } 54 | 55 | func Test_isURL(t *testing.T) { 56 | type args struct { 57 | s string 58 | } 59 | tests := []struct { 60 | name string 61 | args args 62 | want bool 63 | }{ 64 | { 65 | name: "Test https url (low case)", 66 | args: args{s: "https://www.google.com"}, 67 | want: true, 68 | }, 69 | { 70 | name: "Test http url (low case)", 71 | args: args{s: "http://www.google.com"}, 72 | want: true, 73 | }, 74 | { 75 | name: "Test https url (upper case)", 76 | args: args{s: "HTTPS://WWW.GOOGLE.COM"}, 77 | want: true, 78 | }, 79 | { 80 | name: "Test http url (upper case)", 81 | args: args{s: "HTTP://WWW.GOOGLE.COM"}, 82 | want: true, 83 | }, 84 | { 85 | name: "Short", 86 | args: args{s: "HTTP"}, 87 | want: false, 88 | }, 89 | { 90 | name: "Check file path", 91 | args: args{s: "/etc/hosts"}, 92 | want: false, 93 | }, 94 | } 95 | for _, tt := range tests { 96 | t.Run(tt.name, func(t *testing.T) { 97 | if got := isURL(tt.args.s); got != tt.want { 98 | t.Errorf("isURL() = %v, want %v", got, tt.want) 99 | } 100 | }) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /internal/aem/replication/page.go: -------------------------------------------------------------------------------- 1 | package replication 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/go-http-utils/headers" 7 | "github.com/jlentink/aem/internal/aem/objects" 8 | "github.com/jlentink/aem/internal/http" 9 | "mime/multipart" 10 | ) 11 | 12 | const ( 13 | replicationURL = "/bin/replicate.json" 14 | treeReplicationURL = "/etc/replication/treeactivation.html" 15 | ) 16 | 17 | // Activate page on instance 18 | func Activate(i *objects.Instance, path string) ([]byte, error) { 19 | return pageAction(i, path, "activate") 20 | } 21 | 22 | // Deactivate page on instance 23 | func Deactivate(i *objects.Instance, path string) ([]byte, error) { 24 | return pageAction(i, path, "deactivate") 25 | } 26 | 27 | func pageAction(i *objects.Instance, path, command string) ([]byte, error) { 28 | body := &bytes.Buffer{} 29 | writer := multipart.NewWriter(body) 30 | writer.WriteField("path", path) // nolint: errcheck 31 | writer.WriteField("cmd", command) // nolint: errcheck 32 | writer.Close() // nolint: errcheck 33 | 34 | pw, err := i.GetPassword() 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | bodyContent, _, err := http.PostPlainWithHeaders(i.URLString()+replicationURL, i.Username, pw, body, 40 | []http.Header{{Key: headers.ContentType, Value: writer.FormDataContentType()}}) 41 | 42 | return bodyContent, err 43 | } 44 | 45 | // ActivateTree on instance 46 | func ActivateTree(i *objects.Instance, path string, ignoreDeactivated, onlyModified bool) ([]byte, error) { 47 | body := &bytes.Buffer{} 48 | writer := multipart.NewWriter(body) 49 | writer.WriteField("cmd", "activate") // nolint: errcheck 50 | writer.WriteField("ignoredeactivated", fmt.Sprintf("%t", ignoreDeactivated)) // nolint: errcheck 51 | writer.WriteField("onlymodified", fmt.Sprintf("%t", onlyModified)) // nolint: errcheck 52 | writer.WriteField("path", path) // nolint: errcheck 53 | writer.Close() // nolint: errcheck 54 | 55 | pw, err := i.GetPassword() 56 | if err != nil { 57 | return nil, err 58 | } 59 | bodyContent, _, err := http.PostPlainWithHeaders(i.URLString()+treeReplicationURL, i.Username, pw, body, 60 | []http.Header{{Key: headers.ContentType, Value: writer.FormDataContentType()}}) 61 | 62 | return bodyContent, err 63 | } 64 | -------------------------------------------------------------------------------- /internal/commands/packageUpload.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/aem" 5 | "github.com/jlentink/aem/internal/aem/pkg" 6 | "github.com/jlentink/aem/internal/output" 7 | "github.com/spf13/cobra" 8 | "os" 9 | ) 10 | 11 | type commandPackageUpload struct { 12 | verbose bool 13 | force bool 14 | install bool 15 | path string 16 | instanceName string 17 | instanceGroup string 18 | } 19 | 20 | func (c *commandPackageUpload) setup() *cobra.Command { 21 | cmd := &cobra.Command{ 22 | Use: "upload", 23 | Aliases: []string{"up"}, 24 | Short: "Upload package to aem", 25 | PreRun: c.preRun, 26 | Run: c.run, 27 | } 28 | cmd.Flags().StringVarP(&c.instanceName, "name", "n", aem.GetDefaultInstanceName(), "Instance to install package on") 29 | cmd.Flags().StringVarP(&c.instanceGroup, "group", "g", ``, "Group to install package on") 30 | cmd.Flags().StringVarP(&c.path, "package", "p", ``, "Path to package") 31 | cmd.Flags().BoolVarP(&c.force, "force", "f", false, "Force upload") 32 | cmd.Flags().BoolVarP(&c.install, "install", "i", false, "Install package") 33 | cmd.MarkFlagRequired("package") // nolint: errcheck 34 | cmd.MarkFlagFilename("package", "*.zip") // nolint: errcheck 35 | return cmd 36 | } 37 | 38 | func (c *commandPackageUpload) preRun(cmd *cobra.Command, args []string) { 39 | c.verbose, _ = cmd.Flags().GetBool("verbose") 40 | output.SetVerbose(c.verbose) 41 | 42 | ConfigCheckListProjects() 43 | RegisterProject() 44 | } 45 | 46 | func (c *commandPackageUpload) run(cmd *cobra.Command, args []string) { 47 | _, i, errorString, err := getConfigAndInstanceOrGroupWithRoles(c.instanceName, c.instanceGroup, []string{aem.RoleAuthor, aem.RolePublisher}) 48 | if err != nil { 49 | output.Printf(output.NORMAL, errorString, err.Error()) 50 | os.Exit(ExitError) 51 | } 52 | 53 | for _, instance := range i { 54 | output.Printf(output.NORMAL, "\U0001F4A8 Uploading %s to: %s (install %t, force: %t)\n", c.path, instance.Name, c.install, c.force) 55 | crx, htmlBody, err := pkg.Upload(instance, c.path, c.install, c.force) 56 | if err != nil { 57 | output.Printf(output.NORMAL, "\U0000274C %s\n", err.Error()) 58 | if len(htmlBody) > 0 { 59 | output.Printf(output.NORMAL, "%s\n", htmlBody); 60 | } 61 | os.Exit(ExitError) 62 | } 63 | output.Printf(output.VERBOSE, "%s", crx.Response.Data.Log.Text) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /internal/commands/cdnPurgeService.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/fastly/go-fastly/v3/fastly" 5 | "github.com/jlentink/aem/internal/aem/objects" 6 | "github.com/jlentink/aem/internal/output" 7 | "github.com/spf13/cobra" 8 | "os" 9 | "strings" 10 | ) 11 | 12 | type commandCdnPurgeService struct { 13 | verbose bool 14 | cmd *cobra.Command 15 | name string 16 | apiKey string 17 | 18 | } 19 | 20 | func (c *commandCdnPurgeService) setup() *cobra.Command { 21 | c.cmd = &cobra.Command{ 22 | Use: "purge-service", 23 | Aliases: []string{}, 24 | Short: "Purge CDN service", 25 | PreRun: c.preRun, 26 | Run: c.run, 27 | } 28 | 29 | c.cmd.Flags().StringVarP(&c.name, "name", "n", "", "CDN name") 30 | c.cmd.Flags().StringVarP(&c.apiKey, "credentials", "c", "", "API key / Credentials") 31 | c.cmd.MarkFlagRequired("name") // nolint: errcheck 32 | 33 | 34 | 35 | return c.cmd 36 | } 37 | 38 | func (c *commandCdnPurgeService) preRun(cmd *cobra.Command, args []string) { 39 | c.verbose, _ = cmd.Flags().GetBool("verbose") 40 | output.SetVerbose(verbose) 41 | 42 | ConfigCheckListProjects() 43 | RegisterProject() 44 | } 45 | 46 | func (c *commandCdnPurgeService) run(cmd *cobra.Command, args []string) { 47 | cdn, err := getCdnConfig(c.name) 48 | if err != nil { 49 | output.Printf(output.NORMAL, "%s", err.Error()) 50 | os.Exit(ExitError) 51 | } 52 | 53 | if strings.EqualFold(cdn.CdnType, "fastly") { 54 | c.fastlyPurgeService(cdn) 55 | } 56 | } 57 | 58 | func (c *commandCdnPurgeService) fastlyPurgeService(cdn *objects.CDN) { 59 | 60 | if c.apiKey == "" { 61 | apiKey, err := cdn.GetAPIKey() 62 | if err != nil { 63 | output.Printf(output.NORMAL, "%s", err.Error()) 64 | os.Exit(ExitError) 65 | } 66 | c.apiKey = apiKey 67 | } 68 | 69 | client, err := fastly.NewClient(c.apiKey) 70 | if err != nil { 71 | output.Printf(output.NORMAL, "%s", err.Error()) 72 | os.Exit(ExitError) 73 | } 74 | 75 | purge, err := client.PurgeAll(&fastly.PurgeAllInput{ 76 | ServiceID: cdn.ServiceID, 77 | }) 78 | 79 | if err != nil { 80 | output.Printf(output.NORMAL, "%s", err.Error()) 81 | os.Exit(ExitError) 82 | } 83 | if strings.EqualFold(purge.Status, "ok") { 84 | output.Printf(output.NORMAL, "🚮 %s purged - %s", cdn.ServiceID, purge.ID) 85 | os.Exit(ExitNormal) 86 | } 87 | output.Printf(output.NORMAL, "🤬 Error purging - %s", purge.Status) 88 | os.Exit(ExitError) 89 | } 90 | -------------------------------------------------------------------------------- /internal/commands/oakConsole.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/aem" 5 | "github.com/jlentink/aem/internal/cli/oak" 6 | "github.com/jlentink/aem/internal/cli/project" 7 | "github.com/jlentink/aem/internal/output" 8 | "github.com/spf13/cobra" 9 | "os" 10 | ) 11 | 12 | type commandOakConsole struct { 13 | verbose bool 14 | instanceName string 15 | aemVersion string 16 | oakVersion string 17 | writeMode bool 18 | metrics bool 19 | } 20 | 21 | func (c *commandOakConsole) setup() *cobra.Command { 22 | cmd := &cobra.Command{ 23 | Use: "console", 24 | Aliases: []string{}, 25 | Short: "Run oak console", 26 | PreRun: c.preRun, 27 | Run: c.run, 28 | } 29 | cmd.Flags().StringVarP(&c.instanceName, "name", "n", aem.GetDefaultInstanceName(), "Instance to stop") 30 | cmd.Flags().StringVarP(&c.aemVersion, "aem", "a", ``, "Version of AEM to use oak-run on. (use matching AEM version of oak-run)") 31 | cmd.Flags().StringVarP(&c.oakVersion, "oak", "o", ``, "Define version of oak-run to use") 32 | cmd.Flags().BoolVarP(&c.writeMode, "read-write", "w", false, "Connect to repository in read-write mode") 33 | cmd.Flags().BoolVarP(&c.metrics, "metrics", "m", false, "Enables metrics based statistics collection") 34 | cmd.MarkFlagRequired("name") // nolint: errcheck 35 | return cmd 36 | } 37 | 38 | func (c *commandOakConsole) preRun(cmd *cobra.Command, args []string) { 39 | c.verbose, _ = cmd.Flags().GetBool("verbose") 40 | output.SetVerbose(verbose) 41 | 42 | ConfigCheckListProjects() 43 | RegisterProject() 44 | } 45 | 46 | func (c *commandOakConsole) run(cmd *cobra.Command, args []string) { 47 | _, i, errorString, err := getConfigAndInstance(c.instanceName) 48 | if err != nil { 49 | output.Printf(output.NORMAL, errorString, err.Error()) 50 | os.Exit(ExitError) 51 | } 52 | 53 | instancePath, err := project.GetRunDirLocation(*i) 54 | if err != nil { 55 | output.Printf(output.NORMAL, errorString, err.Error()) 56 | os.Exit(ExitError) 57 | } 58 | 59 | oak.SetDefaultVersion(aem.Cnf.OakVersion) 60 | path, _ := oak.Get(i.GetVersion(), aem.Cnf.OakVersion) 61 | oakArgs := []string{"console", instancePath + oak.RepoPath} 62 | if c.writeMode { 63 | oakArgs = append(oakArgs, "--read-write") 64 | } 65 | if c.metrics { 66 | oakArgs = append(oakArgs, "--metrics") 67 | } 68 | oak.Execute(path, aem.Cnf.OakOptions, oakArgs) // nolint: errcheck 69 | 70 | } 71 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | before: 2 | hooks: 3 | - go mod download 4 | # - go generate ./... 5 | - golint -set_exit_status ./... 6 | - make packr 7 | - ./scripts/completions.sh 8 | env_files: 9 | github_token: ~/GITHUB_TOKEN 10 | builds: 11 | - env: 12 | - CGO_ENABLED=0 13 | goos: 14 | - linux 15 | - windows 16 | - darwin 17 | goarch: 18 | - 386 19 | - amd64 20 | - arm64 21 | ignore: 22 | - goos: windows 23 | goarch: arm64 24 | ldflags: 25 | - -X github.com/jlentink/aem/internal/version.commit={{.Commit}} -X github.com/jlentink/aem/internal/version.date={{.Date}} -X github.com/jlentink/aem/internal/version.Main={{.Version}} -w -s 26 | mod_timestamp: '{{ .CommitTimestamp }}' 27 | archives: 28 | - name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' 29 | replacements: 30 | darwin: MacOS 31 | linux: Linux 32 | windows: Windows 33 | 386: i386 34 | amd64: x86_64 35 | format_overrides: 36 | - goos: windows 37 | format: zip 38 | files: 39 | - README.md 40 | - LICENSE.md 41 | - completions/* 42 | checksum: 43 | name_template: 'checksums.txt' 44 | changelog: 45 | sort: asc 46 | filters: 47 | exclude: 48 | - '^docs:' 49 | - '^test:' 50 | snapshot: 51 | name_template: "{{ .Tag }}-SNAPSHOT-{{.ShortCommit}}" 52 | brews: 53 | - tap: 54 | owner: jlentink 55 | name: homebrew-aem 56 | homepage: https://github.com/jlentink/aem 57 | description: Command line tool for AEM 58 | license: GPL-2.0 License 59 | test: | 60 | system "#{bin}/aem -v" 61 | dependencies: 62 | - name: go 63 | install: |- 64 | bin.install "aem" 65 | bash_completion.install "completions/aem.bash" => "aem" 66 | zsh_completion.install "completions/aem.zsh" => "_aem" 67 | dockers: 68 | - 69 | goos: linux 70 | goarch: amd64 71 | image_templates: 72 | - "jlentink/aem-dispatcher:4.3.2" 73 | build_flag_templates: 74 | - "--pull" 75 | - "--label=org.opencontainers.image.created={{.Date}}" 76 | - "--label=org.opencontainers.image.title={{.ProjectName}}" 77 | - "--label=org.opencontainers.image.revision={{.FullCommit}}" 78 | - "--label=org.opencontainers.image.version={{.Version}}" 79 | extra_files: 80 | - docker/assets 81 | skip_push: false 82 | dockerfile: Dockerfile -------------------------------------------------------------------------------- /internal/commands/replicationPage.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/aem" 5 | "github.com/jlentink/aem/internal/aem/replication" 6 | "github.com/jlentink/aem/internal/output" 7 | "github.com/spf13/cobra" 8 | "os" 9 | ) 10 | 11 | type commandReplicationPage struct { 12 | verbose bool 13 | instanceName string 14 | instanceGroup string 15 | path string 16 | activate bool 17 | deactivate bool 18 | cmd *cobra.Command 19 | } 20 | 21 | func (c *commandReplicationPage) setup() *cobra.Command { 22 | c.cmd = &cobra.Command{ 23 | Use: "page", 24 | Short: "Activate / Deactivate page", 25 | PreRun: c.preRun, 26 | Run: c.run, 27 | } 28 | c.cmd.Flags().StringVarP(&c.instanceName, "name", "n", aem.GetDefaultInstanceName(), "Instance to (de)activate page on") 29 | c.cmd.Flags().StringVarP(&c.instanceGroup, "group", "g", ``, "Instance group to (de)activate page on") 30 | c.cmd.Flags().StringVarP(&c.path, "path", "p", ``, "Path to (de)activate") 31 | c.cmd.Flags().BoolVarP(&c.activate, "activate", "a", false, "Activate page") 32 | c.cmd.Flags().BoolVarP(&c.deactivate, "deactivate", "d", false, "Deactivate") 33 | c.cmd.MarkFlagRequired("path") // nolint: errcheck 34 | return c.cmd 35 | } 36 | 37 | func (c *commandReplicationPage) preRun(cmd *cobra.Command, args []string) { 38 | c.verbose, _ = cmd.Flags().GetBool("verbose") 39 | output.SetVerbose(c.verbose) 40 | 41 | ConfigCheckListProjects() 42 | RegisterProject() 43 | } 44 | 45 | func (c *commandReplicationPage) run(cmd *cobra.Command, args []string) { 46 | _, is, errorString, err := getConfigAndInstanceOrGroupWithRoles(c.instanceName, c.instanceGroup, []string{aem.RoleAuthor}) 47 | if err != nil { 48 | output.Printf(output.NORMAL, errorString, err.Error()) 49 | os.Exit(ExitError) 50 | } 51 | 52 | for _, i := range is { 53 | i := i 54 | if c.activate { 55 | _, err := replication.Activate(&i, c.path) 56 | if err != nil { 57 | output.Printf(output.NORMAL, "Could not activate page: %s", err.Error()) 58 | os.Exit(ExitError) 59 | } 60 | output.Printf(output.NORMAL, "\U00002705 Page activated: %s\n", c.path) 61 | } else if c.deactivate { 62 | _, err := replication.Deactivate(&i, c.path) 63 | if err != nil { 64 | output.Printf(output.NORMAL, "Could not activate page: %s", err.Error()) 65 | os.Exit(ExitError) 66 | } 67 | output.Printf(output.NORMAL, "\U00002705 Page deactivated: %s\n", c.path) 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /internal/aem/indexes/indexes.go: -------------------------------------------------------------------------------- 1 | package indexes 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jlentink/aem/internal/aem" 6 | "github.com/jlentink/aem/internal/aem/objects" 7 | "github.com/jlentink/aem/internal/http" 8 | "github.com/tidwall/gjson" 9 | ) 10 | 11 | func getStringArrayValue(result gjson.Result, path string) []string { 12 | if !result.Get(path).Exists() { 13 | return nil 14 | } 15 | 16 | if result.Get(path).IsArray() { 17 | values := make([]string, 0) 18 | for _, arrValue := range result.Get(path).Array() { 19 | values = append(values, arrValue.String()) 20 | } 21 | return values 22 | } 23 | 24 | return []string{result.Get(path).String()} 25 | } 26 | 27 | // GetIndexes retrieves the indexes from a aem instance 28 | func GetIndexes(instance *objects.Instance) ([]*Index, error) { 29 | 30 | pw, err := instance.GetPassword() 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | if !aem.Cnf.ValidateSSL { 36 | http.DisableSSLValidation() 37 | } 38 | 39 | resp, err := http.GetPlain(instance.URLString()+indexes, instance.Username, pw) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | result := gjson.ParseBytes(resp) 45 | indexes := make([]*Index, 0) 46 | rMap := result.Map() 47 | for k := range rMap { 48 | cResult := result.Get(k) 49 | if cResult.Type == gjson.JSON { 50 | isIndex := cResult.Get("jcr:primaryType") 51 | if isIndex.Type == gjson.String && isIndex.Str == "oak:QueryIndexDefinition" { 52 | index := Index{} 53 | index.Info = cResult.Get("info").String() 54 | index.Name = k 55 | index.Type = cResult.Get("type").String() 56 | index.ReindexCount = cResult.Get("reindexCount").Int() 57 | index.Async = getStringArrayValue(cResult, "async") 58 | index.ExcludedPaths = getStringArrayValue(cResult, "excludedPaths") 59 | index.IncludedPaths = getStringArrayValue(cResult, "includedPaths") 60 | indexes = append(indexes, &index) 61 | } 62 | } 63 | } 64 | return indexes, nil 65 | } 66 | 67 | // Reindex start reindex of indexed on aem instance 68 | func Reindex(instance *objects.Instance, index string) error { 69 | pw, err := instance.GetPassword() 70 | if err != nil { 71 | return err 72 | } 73 | 74 | if !aem.Cnf.ValidateSSL { 75 | http.DisableSSLValidation() 76 | } 77 | 78 | _, _, err = http.PostMultiPart(instance.URLString()+fmt.Sprintf(reindexURL, index), instance.Username, pw, map[string]string{"reindex" : "true"}) 79 | 80 | if err != nil { 81 | return err 82 | } 83 | return nil 84 | } 85 | -------------------------------------------------------------------------------- /internal/aem/objects/Description.go: -------------------------------------------------------------------------------- 1 | package objects 2 | 3 | // Description of pkg in aem 4 | type Description struct { 5 | Pid string `json:"pid"` 6 | Path string `json:"path"` 7 | Name string `json:"name"` 8 | DownloadName string `json:"downloadName"` 9 | Group string `json:"group"` 10 | GroupTitle string `json:"groupTitle"` 11 | Version string `json:"version"` 12 | Description string `json:"description,omitempty"` 13 | Thumbnail string `json:"thumbnail"` 14 | BuildCount uint `json:"buildCount"` 15 | Created int64 `json:"created,omitempty"` 16 | CreatedBy string `json:"createdBy,omitempty"` 17 | LastUnpacked int64 `json:"lastUnpacked,omitempty"` 18 | LastUnpackedBy string `json:"lastUnpackedBy,omitempty"` 19 | LastUnwrapped int64 `json:"lastUnwrapped,omitempty"` 20 | Size uint64 `json:"size"` 21 | HasSnapshot bool `json:"hasSnapshot"` 22 | NeedsRewrap bool `json:"needsRewrap"` 23 | RequiresRoot bool `json:"requiresRoot"` 24 | RequiresRestart bool `json:"requiresRestart"` 25 | AcHandling string `json:"acHandling"` 26 | Dependencies []interface{} `json:"dependencies"` 27 | Resolved bool `json:"resolved"` 28 | Filter []Filter `json:"filter"` 29 | Screenshots []interface{} `json:"screenshots"` 30 | LastModified int64 `json:"lastModified,omitempty"` 31 | LastModifiedBy string `json:"lastModifiedBy,omitempty"` 32 | LastWrapped int64 `json:"lastWrapped,omitempty"` 33 | LastWrappedBy string `json:"lastWrappedBy,omitempty"` 34 | LastUnwrappedBy string `json:"lastUnwrappedBy,omitempty"` 35 | BuiltWith string `json:"builtWith,omitempty"` 36 | TestedWith string `json:"testedWith,omitempty"` 37 | FixedBugs string `json:"fixedBugs,omitempty"` 38 | ProviderName string `json:"providerName,omitempty"` 39 | ProviderURL string `json:"providerUrl,omitempty"` 40 | ProviderLink string `json:"providerLink,omitempty"` 41 | } 42 | 43 | // Filter filter of pkg 44 | type Filter struct { 45 | Root string `json:"root"` 46 | Rules []Rules `json:"rules"` 47 | } 48 | 49 | // Rules of filter in pkg 50 | type Rules struct { 51 | Modifier string `json:"modifier"` 52 | Pattern string `json:"pattern"` 53 | } 54 | -------------------------------------------------------------------------------- /internal/commands/bundleStop.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/aem" 5 | "github.com/jlentink/aem/internal/aem/bundle" 6 | "github.com/jlentink/aem/internal/output" 7 | "github.com/spf13/cobra" 8 | "os" 9 | ) 10 | 11 | type commandBundelStop struct { 12 | verbose bool 13 | instanceName string 14 | instanceGroup string 15 | bundle string 16 | } 17 | 18 | func (c *commandBundelStop) setup() *cobra.Command { 19 | cmd := &cobra.Command{ 20 | Use: "stop", 21 | Short: "Stop bundle", 22 | PreRun: c.preRun, 23 | Run: c.run, 24 | } 25 | cmd.Flags().StringVarP(&c.instanceName, "name", "n", aem.GetDefaultInstanceName(), "Instance to install bundle on") 26 | cmd.Flags().StringVarP(&c.instanceGroup, "group", "g", ``, "Instance group to install bundle on") 27 | cmd.Flags().StringVarP(&c.bundle, "bundle", "b", ``, "Instance group to install bundle on") 28 | return cmd 29 | } 30 | 31 | func (c *commandBundelStop) preRun(cmd *cobra.Command, args []string) { 32 | c.verbose, _ = cmd.Flags().GetBool("verbose") 33 | output.SetVerbose(c.verbose) 34 | 35 | ConfigCheckListProjects() 36 | RegisterProject() 37 | } 38 | 39 | func (c *commandBundelStop) run(cmd *cobra.Command, args []string) { 40 | _, is, errorString, err := getConfigAndInstanceOrGroupWithRoles(c.instanceName, c.instanceGroup, []string{aem.RoleAuthor, aem.RolePublisher}) 41 | if err != nil { 42 | output.Printf(output.NORMAL, errorString, err.Error()) 43 | os.Exit(ExitError) 44 | } 45 | 46 | for idx, i := range is { 47 | i := i 48 | if idx == 0 && c.bundle == "" { 49 | bundleObject, err := bundle.Search(&i, "Stopping") 50 | if err != nil { 51 | output.Printf(output.NORMAL, "Could not list bundles: %s", err.Error()) 52 | os.Exit(ExitError) 53 | } 54 | c.bundle = bundleObject.SymbolicName 55 | } 56 | bndl, err := bundle.Get(&i, c.bundle) 57 | if err != nil { 58 | output.Printf(output.NORMAL, "Could not find bundle on: %s", i.Name) 59 | os.Exit(ExitError) 60 | } 61 | 62 | b, err := bundle.Stop(&i, bndl) 63 | if err != nil { 64 | output.Printf(output.NORMAL, "Could not stop bundle %s", err.Error()) 65 | os.Exit(ExitError) 66 | } 67 | 68 | if b.StateRaw == 32 { 69 | output.Printf(output.NORMAL, "\U0001F631 Bundle %s - %s\n", b.SymbolicName, bundle.BundleRawState[b.StateRaw]) 70 | os.Exit(ExitError) 71 | } 72 | output.Printf(output.NORMAL, "\U00002705 Bundle %s - %s\n", b.SymbolicName, bundle.BundleRawState[b.StateRaw]) 73 | os.Exit(ExitNormal) 74 | 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /internal/commands/bundleStart.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/aem" 5 | "github.com/jlentink/aem/internal/aem/bundle" 6 | "github.com/jlentink/aem/internal/output" 7 | "github.com/spf13/cobra" 8 | "os" 9 | ) 10 | 11 | type commandBundleStart struct { 12 | verbose bool 13 | instanceName string 14 | instanceGroup string 15 | bundle string 16 | } 17 | 18 | func (c *commandBundleStart) setup() *cobra.Command { 19 | cmd := &cobra.Command{ 20 | Use: "start", 21 | Short: "Start bundle", 22 | PreRun: c.preRun, 23 | Run: c.run, 24 | } 25 | cmd.Flags().StringVarP(&c.instanceName, "name", "n", aem.GetDefaultInstanceName(), "Instance to install bundle on") 26 | cmd.Flags().StringVarP(&c.instanceGroup, "group", "g", ``, "Instance group to install bundle on") 27 | cmd.Flags().StringVarP(&c.bundle, "bundle", "b", ``, "Instance group to install bundle on") 28 | return cmd 29 | } 30 | 31 | func (c *commandBundleStart) preRun(cmd *cobra.Command, args []string) { 32 | c.verbose, _ = cmd.Flags().GetBool("verbose") 33 | output.SetVerbose(c.verbose) 34 | 35 | ConfigCheckListProjects() 36 | RegisterProject() 37 | } 38 | 39 | func (c *commandBundleStart) run(cmd *cobra.Command, args []string) { 40 | _, is, errorString, err := getConfigAndInstanceOrGroupWithRoles(c.instanceName, c.instanceGroup, []string{aem.RoleAuthor, aem.RolePublisher}) 41 | if err != nil { 42 | output.Printf(output.NORMAL, errorString, err.Error()) 43 | os.Exit(ExitError) 44 | } 45 | 46 | for idx, i := range is { 47 | if idx == 0 && c.bundle == "" { 48 | i := i 49 | bundleObject, err := bundle.Search(&i, "Starting") 50 | if err != nil { 51 | output.Printf(output.NORMAL, "Could not list bundles: %s", err.Error()) 52 | os.Exit(ExitError) 53 | } 54 | c.bundle = bundleObject.SymbolicName 55 | } 56 | 57 | i := i 58 | bndl, err := bundle.Get(&i, c.bundle) 59 | if err != nil { 60 | output.Printf(output.NORMAL, "Could not find bundle on: %s", i.Name) 61 | os.Exit(ExitError) 62 | } 63 | 64 | b, err := bundle.Start(&i, bndl) 65 | if err != nil { 66 | output.Printf(output.NORMAL, "Could not start bundle %s", err.Error()) 67 | os.Exit(ExitError) 68 | } 69 | 70 | if b.StateRaw == 32 { 71 | output.Printf(output.NORMAL, "\U00002705 Bundle %s - %s\n", b.SymbolicName, bundle.BundleRawState[b.StateRaw]) 72 | os.Exit(ExitNormal) 73 | } 74 | output.Printf(output.NORMAL, "\U0001F631 Bundle %s - %s\n", b.SymbolicName, bundle.BundleRawState[b.StateRaw]) 75 | os.Exit(ExitError) 76 | 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /internal/commands/cdnPurgeURL.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/fastly/go-fastly/v3/fastly" 5 | "github.com/jlentink/aem/internal/aem/objects" 6 | "github.com/jlentink/aem/internal/output" 7 | "github.com/spf13/cobra" 8 | "os" 9 | "strings" 10 | ) 11 | 12 | type commandCdnPurgeURL struct { 13 | verbose bool 14 | cmd *cobra.Command 15 | name string 16 | url string 17 | apiKey string 18 | soft bool 19 | } 20 | 21 | func (c *commandCdnPurgeURL) setup() *cobra.Command { 22 | c.cmd = &cobra.Command{ 23 | Use: "purge-url", 24 | Aliases: []string{}, 25 | Short: "Purge CDN based on URL", 26 | PreRun: c.preRun, 27 | Run: c.run, 28 | } 29 | 30 | c.cmd.Flags().StringVarP(&c.name, "name", "n", "", "CDN name") 31 | c.cmd.Flags().StringVarP(&c.apiKey, "credentials", "c", "", "API key / Credentials") 32 | c.cmd.Flags().StringVarP(&c.url, "url", "u", "", "url to purge") 33 | c.cmd.Flags().BoolVarP(&c.soft, "soft", "s", false, "Soft purge") 34 | 35 | c.cmd.MarkFlagRequired("name") // nolint: errcheck 36 | c.cmd.MarkFlagRequired("url") // nolint: errcheck 37 | 38 | return c.cmd 39 | } 40 | 41 | func (c *commandCdnPurgeURL) preRun(cmd *cobra.Command, args []string) { 42 | c.verbose, _ = cmd.Flags().GetBool("verbose") 43 | output.SetVerbose(verbose) 44 | 45 | ConfigCheckListProjects() 46 | RegisterProject() 47 | } 48 | 49 | func (c *commandCdnPurgeURL) run(cmd *cobra.Command, args []string) { 50 | cdn, err := getCdnConfig(c.name) 51 | if err != nil { 52 | output.Printf(output.NORMAL, "%s", err.Error()) 53 | os.Exit(ExitError) 54 | } 55 | 56 | if strings.EqualFold(cdn.CdnType, "fastly") { 57 | c.fastlyPurge(cdn) 58 | } 59 | } 60 | 61 | func (c *commandCdnPurgeURL) fastlyPurge(cdn *objects.CDN){ 62 | if c.apiKey == "" { 63 | apiKey, err := cdn.GetAPIKey() 64 | if err != nil { 65 | output.Printf(output.NORMAL, "%s", err.Error()) 66 | os.Exit(ExitError) 67 | } 68 | c.apiKey = apiKey 69 | } 70 | client, err := fastly.NewClient(c.apiKey) 71 | if err != nil { 72 | output.Printf(output.NORMAL, "%s", err.Error()) 73 | os.Exit(ExitError) 74 | } 75 | 76 | purge, err := client.Purge(&fastly.PurgeInput{ 77 | URL: c.url, 78 | Soft: c.soft, 79 | }) 80 | if err != nil { 81 | output.Printf(output.NORMAL, "%s", err.Error()) 82 | os.Exit(ExitError) 83 | } 84 | 85 | if strings.EqualFold(purge.Status, "ok") { 86 | output.Printf(output.NORMAL, "🚮 %s purged - %s", c.url, purge.ID) 87 | os.Exit(ExitNormal) 88 | } 89 | output.Printf(output.NORMAL, "🤬 Error purging - %s", purge.Status) 90 | os.Exit(ExitError) 91 | } 92 | -------------------------------------------------------------------------------- /internal/commands/packageList.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "github.com/dustin/go-humanize" 6 | "github.com/jedib0t/go-pretty/table" 7 | "github.com/jlentink/aem/internal/aem" 8 | "github.com/jlentink/aem/internal/aem/objects" 9 | _package "github.com/jlentink/aem/internal/aem/pkg" 10 | "github.com/jlentink/aem/internal/output" 11 | "github.com/spf13/cobra" 12 | "os" 13 | ) 14 | 15 | type commandPackageList struct { 16 | verbose bool 17 | plain bool 18 | instanceName string 19 | group string 20 | } 21 | 22 | func (c *commandPackageList) setup() *cobra.Command { 23 | cmd := &cobra.Command{ 24 | Use: "list", 25 | Short: "List packages", 26 | Aliases: []string{}, 27 | PreRun: c.preRun, 28 | Run: c.run, 29 | } 30 | cmd.Flags().StringVarP(&c.instanceName, "name", "n", aem.GetDefaultInstanceName(), "Instance to stop") 31 | cmd.Flags().StringVarP(&c.group, "group", "g", "", "Group to get") 32 | cmd.Flags().BoolVarP(&c.plain, "plain", "", false, "Output as CSV") 33 | cmd.MarkFlagRequired("name") // nolint: errcheck 34 | return cmd 35 | } 36 | 37 | func (c *commandPackageList) preRun(cmd *cobra.Command, args []string) { 38 | c.verbose, _ = cmd.Flags().GetBool("verbose") 39 | output.SetVerbose(c.verbose) 40 | 41 | ConfigCheckListProjects() 42 | RegisterProject() 43 | } 44 | 45 | func (c *commandPackageList) run(cmd *cobra.Command, args []string) { 46 | _, i, errorString, err := getConfigAndInstance(c.instanceName) 47 | if err != nil { 48 | output.Printf(output.NORMAL, errorString, err.Error()) 49 | os.Exit(ExitError) 50 | } 51 | pkgs, err := _package.FilteredByGroupPackageList(*i, c.group) 52 | if err != nil { 53 | output.Printf(output.NORMAL, "Could not get list from instance: %s", err.Error()) 54 | os.Exit(ExitError) 55 | 56 | } 57 | if c.plain == true { 58 | renderPlain(pkgs) 59 | } else { 60 | renderFancy(pkgs) 61 | } 62 | } 63 | 64 | func renderPlain(pkgs []objects.Package){ 65 | for _, cP := range pkgs { 66 | fmt.Printf("%s,%s,%s,%s\n", cP.Name, cP.Version, cP.Group, cP.DownloadName) 67 | } 68 | } 69 | 70 | func renderFancy(pkgs []objects.Package){ 71 | t := table.NewWriter() 72 | t.SetOutputMirror(os.Stdout) 73 | t.AppendHeader(table.Row{"#", "Name", "Version", "Group", "Size", "Last modified"}) 74 | 75 | for i, cP := range pkgs { 76 | e := output.UnixTime(cP.LastModified) 77 | tt := "" 78 | if e != nil { 79 | //nolint 80 | tt = fmt.Sprintf("%s", e.UTC()) 81 | } 82 | t.AppendRow([]interface{}{i, cP.Name, cP.Version, cP.Group, humanize.Bytes(uint64(cP.Size)), tt}) 83 | } 84 | t.Render() 85 | } 86 | -------------------------------------------------------------------------------- /internal/commands/cdnPurgeTag.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/fastly/go-fastly/v3/fastly" 5 | "github.com/jlentink/aem/internal/aem/objects" 6 | "github.com/jlentink/aem/internal/output" 7 | "github.com/spf13/cobra" 8 | "os" 9 | "strings" 10 | ) 11 | 12 | type commandCdnPurgeTag struct { 13 | verbose bool 14 | cmd *cobra.Command 15 | name string 16 | apiKey string 17 | tags string 18 | soft bool 19 | } 20 | 21 | func (c *commandCdnPurgeTag) setup() *cobra.Command { 22 | c.cmd = &cobra.Command{ 23 | Use: "purge-tag", 24 | Aliases: []string{"purge-tags"}, 25 | Short: "Purge cdn based on tag.", 26 | PreRun: c.preRun, 27 | Run: c.run, 28 | } 29 | 30 | c.cmd.Flags().StringVarP(&c.name, "name", "n", "", "CDN name") 31 | c.cmd.Flags().StringVarP(&c.apiKey, "credentials", "c", "", "API key / Credentials") 32 | c.cmd.Flags().StringVarP(&c.tags, "tag", "t", "", "Tag(s) comma seperated") 33 | c.cmd.Flags().BoolVarP(&c.soft, "soft", "s", false, "Soft purge") 34 | c.cmd.MarkFlagRequired("name") // nolint: errcheck 35 | return c.cmd 36 | } 37 | 38 | func (c *commandCdnPurgeTag) preRun(cmd *cobra.Command, args []string) { 39 | c.verbose, _ = cmd.Flags().GetBool("verbose") 40 | output.SetVerbose(verbose) 41 | 42 | ConfigCheckListProjects() 43 | RegisterProject() 44 | } 45 | 46 | func (c *commandCdnPurgeTag) run(cmd *cobra.Command, args []string) { 47 | cdn, err := getCdnConfig(c.name) 48 | if err != nil { 49 | output.Printf(output.NORMAL, "%s", err.Error()) 50 | os.Exit(ExitError) 51 | } 52 | 53 | if strings.EqualFold(cdn.CdnType, "fastly") { 54 | c.fastlyPurgeTag(cdn) 55 | } 56 | } 57 | 58 | func (c *commandCdnPurgeTag) fastlyPurgeTag(cdn *objects.CDN){ 59 | if c.apiKey == "" { 60 | apiKey, err := cdn.GetAPIKey() 61 | if err != nil { 62 | output.Printf(output.NORMAL, "%s", err.Error()) 63 | os.Exit(ExitError) 64 | } 65 | c.apiKey = apiKey 66 | } 67 | client, err := fastly.NewClient(c.apiKey) 68 | if err != nil { 69 | output.Printf(output.NORMAL, "%s", err.Error()) 70 | os.Exit(ExitError) 71 | } 72 | 73 | tags := strings.Split(c.tags, ",") 74 | 75 | purge, err := client.PurgeKeys(&fastly.PurgeKeysInput{ 76 | ServiceID: cdn.ServiceID, 77 | Keys: tags, 78 | Soft: c.soft, 79 | }) 80 | if err != nil { 81 | output.Printf(output.NORMAL, "%s", err.Error()) 82 | os.Exit(ExitError) 83 | } 84 | if err == nil { 85 | output.Printf(output.NORMAL, "🚮 purged %s - %s", cdn.ServiceID, c.tags) 86 | os.Exit(ExitNormal) 87 | } 88 | output.Printf(output.NORMAL, "🤬 Error purging - %s", purge) 89 | os.Exit(ExitError) 90 | } -------------------------------------------------------------------------------- /internal/aem/Find_test.go: -------------------------------------------------------------------------------- 1 | package aem 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/aem/objects" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestGetByName(t *testing.T) { 10 | instances := []objects.Instance{ 11 | { 12 | Name: "Some-name", 13 | }, 14 | { 15 | Name: "Some-name #2", 16 | }, 17 | { 18 | Name: "Some-name #3", 19 | }, 20 | } 21 | 22 | type args struct { 23 | n string 24 | i []objects.Instance 25 | } 26 | tests := []struct { 27 | name string 28 | args args 29 | want *objects.Instance 30 | wantErr bool 31 | }{ 32 | { 33 | name: "Find instance #1", 34 | args: args{n: "Some-name #2", i: instances}, 35 | want: &objects.Instance{Name: "Some-name #2"}, 36 | wantErr: false, 37 | }, 38 | { 39 | name: "Find instance #2", 40 | args: args{n: "Some-names", i: instances}, 41 | want: nil, 42 | wantErr: true, 43 | }, 44 | } 45 | for _, tt := range tests { 46 | t.Run(tt.name, func(t *testing.T) { 47 | got, err := GetByName(tt.args.n, tt.args.i) 48 | if (err != nil) != tt.wantErr { 49 | t.Errorf("GetByName() error = %v, wantErr %v", err, tt.wantErr) 50 | return 51 | } 52 | if !reflect.DeepEqual(got, tt.want) { 53 | t.Errorf("GetByName() = %v, want %v", got, tt.want) 54 | } 55 | }) 56 | } 57 | } 58 | 59 | func TestGetByGroup(t *testing.T) { 60 | instances := []objects.Instance{ 61 | { 62 | Name: "Some-name", 63 | Group: "Local", 64 | }, 65 | { 66 | Name: "Some-name #2", 67 | Group: "development", 68 | }, 69 | { 70 | Name: "Some-name #3", 71 | Group: "development", 72 | }, 73 | } 74 | type args struct { 75 | g string 76 | i []objects.Instance 77 | } 78 | tests := []struct { 79 | name string 80 | args args 81 | want []objects.Instance 82 | }{ 83 | { 84 | name: "Find one in group", 85 | args: args{g: "Local", i: instances}, 86 | want: []objects.Instance{{Name: "Some-name", Group: "Local"}}, 87 | }, 88 | { 89 | name: "Find two in group", 90 | args: args{g: "development", i: instances}, 91 | want: []objects.Instance{ 92 | {Name: "Some-name #2", Group: "development"}, 93 | {Name: "Some-name #3", Group: "development"}, 94 | }, 95 | }, 96 | } 97 | for _, tt := range tests { 98 | t.Run(tt.name, func(t *testing.T) { 99 | //got, err := GetByGroup(tt.args.g, tt.args.i); !reflect.DeepEqual(got, tt.want) 100 | //if got { 101 | // t.Errorf("GetByGroup() = %v, want %v", got, tt.want) 102 | //} 103 | }) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /internal/commands/log.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hpcloud/tail" 6 | "github.com/jlentink/aem/internal/aem" 7 | "github.com/jlentink/aem/internal/aem/objects" 8 | "github.com/jlentink/aem/internal/cli/project" 9 | "github.com/jlentink/aem/internal/output" 10 | "github.com/spf13/cobra" 11 | "os" 12 | ) 13 | 14 | type commandLog struct { 15 | verbose bool 16 | instanceName string 17 | listLogs bool 18 | follow bool 19 | log string 20 | buff int64 21 | } 22 | 23 | func (c *commandLog) setup() *cobra.Command { 24 | cmd := &cobra.Command{ 25 | Use: "log", 26 | Aliases: []string{"logs"}, 27 | Short: "List error log or application log", 28 | PreRun: c.preRun, 29 | Run: c.run, 30 | } 31 | cmd.Flags().StringVarP(&c.instanceName, "name", "n", aem.GetDefaultInstanceName(), "Instance to stop") 32 | cmd.Flags().BoolVarP(&c.listLogs, "list", "", false, "List available log files") 33 | cmd.Flags().BoolVarP(&c.follow, "follow", "f", false, "Actively follow lines when they come in") 34 | cmd.Flags().Int64VarP(&c.buff, "buffer", "b", 1000, "Buffer to show at the bottom of the file") 35 | cmd.Flags().StringVarP(&c.log, "log", "l", "error.log", "Which file(s) to follow") 36 | return cmd 37 | } 38 | 39 | func (c *commandLog) preRun(cmd *cobra.Command, args []string) { 40 | c.verbose, _ = cmd.Flags().GetBool("verbose") 41 | output.SetVerbose(verbose) 42 | 43 | ConfigCheckListProjects() 44 | RegisterProject() 45 | } 46 | 47 | func (c *commandLog) list(i *objects.Instance) { 48 | files, err := aem.ListLogFiles(i) 49 | if err != nil { 50 | output.Printf(output.NORMAL, "Could not list log files (%s)", err.Error()) 51 | os.Exit(ExitError) 52 | } 53 | 54 | output.Printf(output.NORMAL, "Available log files.\n") 55 | for _, file := range files { 56 | if !file.IsDir() { 57 | output.Printf(output.NORMAL, " - %s\n", file.Name()) 58 | } 59 | } 60 | os.Exit(ExitNormal) 61 | } 62 | 63 | func (c *commandLog) run(cmd *cobra.Command, args []string) { 64 | _, i, errorString, err := getConfigAndInstance(c.instanceName) 65 | if err != nil { 66 | output.Printf(output.NORMAL, errorString, err.Error()) 67 | os.Exit(ExitError) 68 | } 69 | 70 | if c.listLogs { 71 | c.list(i) 72 | } 73 | 74 | path, _ := project.GetLogDirLocation(*i) 75 | 76 | if c.buff > 0 { 77 | c.buff *= -1 78 | } 79 | 80 | location := &tail.SeekInfo{ 81 | Offset: c.buff, 82 | Whence: 2, 83 | } 84 | t, _ := tail.TailFile(path+c.log, tail.Config{Follow: c.follow, Location: location, Logger: tail.DiscardingLogger}) 85 | for line := range t.Lines { 86 | fmt.Println(line.Text) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /internal/commands/start.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jlentink/aem/internal/aem" 6 | "github.com/jlentink/aem/internal/aem/dispatcher" 7 | "github.com/jlentink/aem/internal/output" 8 | "github.com/spf13/cobra" 9 | "os" 10 | ) 11 | 12 | type commandStart struct { 13 | verbose bool 14 | instanceName string 15 | groupName string 16 | allowRoot bool 17 | foreground bool 18 | forceDownload bool 19 | ignorePid bool 20 | } 21 | 22 | func (c *commandStart) setup() *cobra.Command { 23 | cmd := &cobra.Command{ 24 | Use: "start", 25 | Short: "Start Adobe Experience Manager instance", 26 | PreRun: c.preRun, 27 | Run: c.run, 28 | } 29 | 30 | cmd.Flags().StringVarP(&c.instanceName, "name", "n", aem.GetDefaultInstanceName(), "Instance to start") 31 | cmd.Flags().StringVarP(&c.groupName, "group", "g", "", "Instance to start") 32 | cmd.Flags().BoolVarP(&c.forceDownload, "download", "d", false, "Force re-download") 33 | cmd.Flags().BoolVarP(&c.foreground, "foreground", "f", false, "on't detach aem from current tty") 34 | cmd.Flags().BoolVarP(&c.allowRoot, "allow-root", "r", false, "Allow to start as root user (UID: 0)") 35 | cmd.Flags().BoolVarP(&c.ignorePid, "ignore-pid", "p", false, "Ignore existing PID file and start AEM") 36 | 37 | return cmd 38 | } 39 | 40 | func (c *commandStart) preRun(cmd *cobra.Command, args []string) { 41 | c.verbose, _ = cmd.Flags().GetBool("verbose") 42 | output.SetVerbose(verbose) 43 | 44 | ConfigCheckListProjects() 45 | RegisterProject() 46 | } 47 | 48 | func (c *commandStart) run(cmd *cobra.Command, args []string) { 49 | if !aem.AllowUserStart(c.allowRoot) { 50 | output.Print(output.NORMAL, "You are starting aem as a root. This is not allowed. override with: --allow-root\n") 51 | os.Exit(ExitError) 52 | } 53 | 54 | cnf, instances, errorString, err := getConfigAndInstanceOrGroupWithRoles(c.instanceName, c.groupName, []string{aem.RoleAuthor, aem.RolePublisher, aem.RoleDispatcher}) 55 | if err != nil { 56 | output.Printf(output.NORMAL, errorString, err.Error()) 57 | os.Exit(ExitError) 58 | } 59 | 60 | for _, currentInstance := range instances { 61 | if currentInstance.InstanceOf([]string{aem.RoleAuthor, aem.RolePublisher}) { 62 | err := aem.FullStart(currentInstance, c.ignorePid, c.forceDownload, c.foreground, cnf, nil) 63 | if err != nil { 64 | os.Exit(ExitError) 65 | } 66 | 67 | } else if currentInstance.InstanceOf([]string{aem.RoleDispatcher}){ 68 | err := dispatcher.Start(currentInstance, cnf, c.foreground) 69 | if err != nil { 70 | fmt.Printf("%s", err.Error()) 71 | os.Exit(ExitError) 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /internal/cli/project/create.go: -------------------------------------------------------------------------------- 1 | package project 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jlentink/aem/internal/aem/objects" 6 | "github.com/spf13/afero" 7 | ) 8 | 9 | //CreateBinDir Create bin dir 10 | func CreateBinDir() (string, error) { 11 | binDir, err := GetBinDir() 12 | if err != nil { 13 | return ``, err 14 | } 15 | if exists, _ := afero.Exists(fs, binDir); !exists { 16 | err := fs.MkdirAll(binDir, 0755) 17 | if err != nil { 18 | return ``, fmt.Errorf("could not create install dir (%s)", binDir) 19 | } 20 | } 21 | return binDir, nil 22 | } 23 | 24 | //CreateInstanceDir Create dir for instance 25 | func CreateInstanceDir(instance objects.Instance) (string, error) { 26 | instanceDir, err := GetInstanceDirLocation(instance) 27 | if err != nil { 28 | return ``, err 29 | } 30 | if exists, _ := afero.Exists(fs, instanceDir); !exists { 31 | err := fs.MkdirAll(instanceDir, 0755) 32 | if err != nil { 33 | return ``, fmt.Errorf("could not create install instance (%s)", instanceDir) 34 | } 35 | } 36 | return instanceDir, nil 37 | } 38 | 39 | // CreateAemInstallDir creates aem install dir 40 | func CreateAemInstallDir(instance objects.Instance) (string, error) { 41 | path, err := GetAemInstallDirLocation(instance) 42 | if err != nil { 43 | return ``, err 44 | } 45 | if exists, _ := afero.Exists(fs, path); !exists { 46 | err := fs.MkdirAll(path, 0755) 47 | if err != nil { 48 | return ``, fmt.Errorf("could not create install dir (%s)", path) 49 | } 50 | } 51 | return path, nil 52 | } 53 | 54 | // CreateInstancesDir creates instances dir for all instances to be created under 55 | func CreateInstancesDir() (string, error) { 56 | path, err := GetInstancesDirLocation() 57 | if err != nil { 58 | return ``, err 59 | } 60 | if exists, _ := afero.Exists(fs, path); !exists { 61 | err := fs.MkdirAll(path, 0755) 62 | if err != nil { 63 | return ``, fmt.Errorf("could not create instance dir (%s)", path) 64 | } 65 | } 66 | return path, nil 67 | } 68 | 69 | // CreateDirForPackage creates a dir for package struct 70 | func CreateDirForPackage(aemPackage *objects.Package) (string, error) { 71 | path, err := GetDirForPackage(aemPackage) 72 | if err != nil { 73 | return ``, err 74 | } 75 | 76 | if exists, _ := afero.Exists(fs, path); !exists { 77 | err := fs.MkdirAll(path, 0755) 78 | if err != nil { 79 | return ``, fmt.Errorf("could not create pkg dir (%s)", path) 80 | } 81 | } 82 | return path, nil 83 | } 84 | 85 | // CreateDir creates a dir for package struct 86 | func CreateDir(path string) (string, error) { 87 | if exists, _ := afero.Exists(fs, path); !exists { 88 | err := fs.MkdirAll(path, 0755) 89 | if err != nil { 90 | return ``, fmt.Errorf("could not create pkg dir (%s)", path) 91 | } 92 | } 93 | return path, nil 94 | } 95 | -------------------------------------------------------------------------------- /internal/aem/dispatcher/start.go: -------------------------------------------------------------------------------- 1 | package dispatcher 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jlentink/aem/internal/aem" 6 | "github.com/jlentink/aem/internal/aem/objects" 7 | "github.com/jlentink/aem/internal/cli/project" 8 | "github.com/jlentink/aem/internal/output" 9 | "os" 10 | "os/exec" 11 | ) 12 | 13 | // Start instance 14 | func Start(i objects.Instance, cnf *objects.Config, forGround bool) error { 15 | if !DaemonRunning() { 16 | return fmt.Errorf("docker daemon is not running") 17 | } 18 | 19 | pwd, err := project.GetWorkDir() 20 | if err != nil { 21 | return fmt.Errorf("could not get working directory") 22 | } 23 | 24 | author, err := aem.GetByName(i.Author, cnf.Instances) 25 | if err != nil { 26 | return err 27 | } 28 | 29 | publisher, err := aem.GetByName(i.Publisher, cnf.Instances) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | dispatcherEndpoint := i.DispatcherEndpoint 35 | if dispatcherEndpoint == "" { 36 | dispatcherEndpoint = "host.docker.internal" 37 | } 38 | 39 | fp, err := project.Create(fmt.Sprintf("%s/dispatcher/src/empty.txt", pwd)) 40 | if err != nil { 41 | return fmt.Errorf("could not create empty file") 42 | } 43 | fp.Close() 44 | 45 | options := []string { 46 | "run", 47 | "--rm", 48 | "-p", 49 | fmt.Sprintf("%d:80", i.Port), 50 | "-p", 51 | fmt.Sprintf("%d:443", i.SPort), 52 | "--detach", 53 | fmt.Sprintf("--name=%s", processName(cnf)), 54 | "-v", 55 | fmt.Sprintf("%s/dispatcher/src/conf:/usr/local/apache2/conf", pwd), 56 | "-v", 57 | fmt.Sprintf("%s/dispatcher/src/conf.d:/usr/local/apache2/conf.d", pwd), 58 | "-v", 59 | fmt.Sprintf("%s/dispatcher/src/conf.dispatcher.d:/usr/local/apache2/conf.dispatcher.d", pwd), 60 | "-v", 61 | fmt.Sprintf("%s/dispatcher/src/conf.modules.d:/usr/local/apache2/conf.modules.d", pwd), 62 | "-v", 63 | fmt.Sprintf("%s/dispatcher/src/empty.txt:/usr/local/apache2/conf.modules.d/00-systemd.conf", pwd), 64 | "-e", 65 | fmt.Sprintf("AUTHOR_IP=%s", dispatcherEndpoint), 66 | "-e", 67 | fmt.Sprintf("AUTHOR_PORT=%d", author.Port), 68 | "-e", 69 | fmt.Sprintf("PUBLISH_IP=%s", dispatcherEndpoint), 70 | "-e", 71 | "CRX_FILTER=allow", 72 | "-e", 73 | fmt.Sprintf("PUBLISH_PORT=%d", publisher.Port), 74 | "-e", 75 | "AUTHOR_DOCROOT=/var/www/author", 76 | "-e", 77 | "PUBLISH_DOCROOT=/var/www/html", 78 | "-e", 79 | "PUBLISH_DEFAULT_HOSTNAME=publish", 80 | "-e", 81 | "AUTHOR_DEFAULT_HOSTNAME=author", 82 | "-e", 83 | fmt.Sprintf("DISP_ID=dispatcher-%s", processName(cnf)), 84 | fmt.Sprintf("jlentink/aem-dispatcher:%s", i.DispatcherVersion), 85 | } 86 | output.Printf(output.VERBOSE, "%s\n", options) 87 | cmd := exec.Command("docker", options...) 88 | 89 | if !forGround { 90 | return cmd.Start() 91 | if err != nil { 92 | return err 93 | } 94 | } 95 | cmd.Stdout = os.Stdout 96 | cmd.Stderr = os.Stderr 97 | return cmd.Run() 98 | } -------------------------------------------------------------------------------- /internal/cli/cachedir/projects.go: -------------------------------------------------------------------------------- 1 | package cachedir 2 | 3 | import ( 4 | "bytes" 5 | "github.com/BurntSushi/toml" 6 | "github.com/jlentink/aem/internal/cli/project" 7 | "github.com/jlentink/aem/internal/output" 8 | "io/ioutil" 9 | "sort" 10 | "strings" 11 | ) 12 | 13 | const projectsFile = "projects.toml" 14 | 15 | // ProjectSorter sorter object 16 | type ProjectSorter []ProjectRegistered 17 | 18 | func (a ProjectSorter) Len() int { return len(a) } 19 | func (a ProjectSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 20 | func (a ProjectSorter) Less(i, j int) bool { return a[i].Name < a[j].Name } 21 | 22 | // Projects Registered projects 23 | type Projects struct { 24 | Project []ProjectRegistered 25 | } 26 | 27 | // ProjectRegistered registered project 28 | type ProjectRegistered struct { 29 | Name string 30 | Path string 31 | } 32 | 33 | // RegisteredProjects registered projects 34 | func RegisteredProjects() []ProjectRegistered { 35 | projects := Projects{} 36 | if project.Exists(getCacheRoot() + "/" + projectsFile) { 37 | toml.DecodeFile(getCacheRoot() + "/" + projectsFile, &projects) // nolint: errcheck 38 | } 39 | return projects.Project 40 | } 41 | 42 | // ProjectsSort sort projects on alphabet 43 | func ProjectsSort(projects []ProjectRegistered) []ProjectRegistered { 44 | sort.Sort(ProjectSorter(projects)) 45 | return projects 46 | } 47 | 48 | // RegisterProject register project in register 49 | func RegisterProject(name, path string){ 50 | Init() 51 | mutated := false 52 | projects := RegisteredProjects() 53 | for index, project := range projects { 54 | if strings.ToLower(project.Name) == strings.ToLower(name) && project.Path == path { 55 | return 56 | } 57 | if strings.ToLower(project.Name) == strings.ToLower(name) { 58 | projects[index].Path = path 59 | mutated = true 60 | } 61 | } 62 | 63 | if !mutated { 64 | projects = append(projects, ProjectRegistered{Name: name, Path: path}) 65 | mutated = true 66 | } 67 | 68 | writeRegisterFile(projects) 69 | } 70 | 71 | // SetProjectMetaData set metadata for project 72 | func SetProjectMetaData(project ProjectRegistered, key, value string) { 73 | 74 | } 75 | 76 | // GetProjectMetaData Get metadata for project 77 | func GetProjectMetaData(project ProjectRegistered, keystring string) { 78 | 79 | } 80 | 81 | func writeRegisterFile(projects []ProjectRegistered) { 82 | data := Projects{Project: projects} 83 | buf := new(bytes.Buffer) 84 | err := toml.NewEncoder(buf).Encode(data) 85 | if err != nil { 86 | output.Printf(output.VERBOSE, "Error encoding projects file: %s", err.Error()) 87 | return 88 | } 89 | 90 | err = ioutil.WriteFile(getCacheRoot() + "/" + projectsFile, buf.Bytes(), 0644) 91 | if err != nil { 92 | output.Printf(output.VERBOSE, "Error writing projects file: %s", err.Error()) 93 | return 94 | } 95 | } 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /internal/commands/pullContent.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/aem" 5 | "github.com/jlentink/aem/internal/aem/pkg" 6 | "github.com/jlentink/aem/internal/cli/project" 7 | "github.com/jlentink/aem/internal/output" 8 | "github.com/spf13/cobra" 9 | "os" 10 | ) 11 | 12 | type commandPullContent struct { 13 | verbose bool 14 | instanceName string 15 | toInstanceName string 16 | build bool 17 | } 18 | 19 | func (c *commandPullContent) setup() *cobra.Command { 20 | cmd := &cobra.Command{ 21 | Use: "pull-content", 22 | Short: "Pull content in from instance via packages", 23 | Aliases: []string{"cpull"}, 24 | PreRun: c.preRun, 25 | Run: c.run, 26 | } 27 | cmd.Flags().StringVarP(&c.instanceName, "from", "f", ``, "Instance to copy from") 28 | cmd.Flags().StringVarP(&c.toInstanceName, "to", "t", aem.GetDefaultInstanceName(), "Destination Instance") 29 | cmd.Flags().BoolVarP(&c.build, "build", "b", false, "Build before download") 30 | return cmd 31 | } 32 | 33 | func (c *commandPullContent) preRun(cmd *cobra.Command, args []string) { 34 | c.verbose, _ = cmd.Flags().GetBool("verbose") 35 | output.SetVerbose(c.verbose) 36 | 37 | ConfigCheckListProjects() 38 | RegisterProject() 39 | } 40 | 41 | func (c *commandPullContent) run(cmd *cobra.Command, args []string) { 42 | cnf, f, errorString, err := getConfigAndInstance(c.instanceName) 43 | if err != nil { 44 | output.Printf(output.NORMAL, errorString, err.Error()) 45 | os.Exit(ExitError) 46 | } 47 | 48 | _, t, errorString, err := getConfigAndInstance(c.toInstanceName) 49 | if err != nil { 50 | output.Printf(output.NORMAL, errorString, err.Error()) 51 | os.Exit(ExitError) 52 | } 53 | 54 | output.Printf(output.NORMAL, "\U0001F69A %s => %s\n", f.Name, t.Name) 55 | for _, cPkg := range cnf.ContentPackages { 56 | if c.build { 57 | dPkg, err := pkg.GetPackageByName(*f, cnf.ContentBackupName) 58 | if dPkg.Name == "" { 59 | pkg.Create(*f, cnf.ContentBackupName, cnf.ContentBackupGroup, pkg.GetTimeVersion(), cnf.ContentBackupPaths, true) 60 | } else if dPkg.Name != "" { 61 | rebuildPackage(f, cPkg) 62 | } else if err != nil { 63 | output.Printf(output.NORMAL, "Could not build package", err.Error()) 64 | os.Exit(ExitError) 65 | } 66 | } 67 | pd, err := pkg.DownloadWithName(f, cPkg) 68 | if err != nil { 69 | output.Printf(output.NORMAL, "\U0000274C Issue while fetching content package: %s\n", err.Error()) 70 | os.Exit(ExitError) 71 | } 72 | path, err := project.GetLocationForPackage(pd) 73 | if err != nil { 74 | output.Printf(output.NORMAL, errorString, err.Error()) 75 | os.Exit(ExitError) 76 | } 77 | 78 | crx, htmlBody, err := pkg.Upload(*t, path, true, true) 79 | if err != nil { 80 | output.Printf(output.NORMAL, errorString, err.Error()) 81 | if len(htmlBody) > 0 { 82 | output.Printf(output.NORMAL, "%s\n", htmlBody) 83 | } 84 | os.Exit(ExitError) 85 | } 86 | output.Printf(output.VERBOSE, "%s", crx.Response.Data.Log.Text) 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /internal/commands/destroy.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jlentink/aem/internal/aem" 6 | "github.com/jlentink/aem/internal/aem/objects" 7 | "github.com/jlentink/aem/internal/aem/pkg" 8 | "github.com/jlentink/aem/internal/cli/project" 9 | "github.com/jlentink/aem/internal/output" 10 | "github.com/spf13/cobra" 11 | "os" 12 | "time" 13 | ) 14 | 15 | type commandDestroy struct { 16 | verbose bool 17 | force bool 18 | create bool 19 | instanceName string 20 | instanceGroup string 21 | backup bool 22 | startLevel string 23 | } 24 | 25 | func (c *commandDestroy) setup() *cobra.Command { 26 | cmd := &cobra.Command{ 27 | Use: "destroy", 28 | Short: "Destroy instance", 29 | PreRun: c.preRun, 30 | Run: c.run, 31 | } 32 | cmd.Flags().StringVarP(&c.instanceName, "name", "n", aem.GetDefaultInstanceName(), "Instance to install bundle on") 33 | cmd.Flags().StringVarP(&c.instanceGroup, "group", "g", ``, "Instance group to install bundle on") 34 | cmd.Flags().BoolVarP(&c.backup, "no-backup", "b", true, "No Backup of content first.") 35 | cmd.Flags().BoolVarP(&c.force, "force", "f", false, "Force delete don't ask for confirmation.") 36 | cmd.Flags().BoolVarP(&c.create, "create", "c", false, "Spin up again after destroy") 37 | return cmd 38 | } 39 | 40 | func (c *commandDestroy) preRun(cmd *cobra.Command, args []string) { 41 | c.verbose, _ = cmd.Flags().GetBool("verbose") 42 | output.SetVerbose(c.verbose) 43 | 44 | ConfigCheckListProjects() 45 | RegisterProject() 46 | } 47 | 48 | func (c *commandDestroy) run(cmd *cobra.Command, args []string) { 49 | config, is, errorString, err := getConfigAndInstanceOrGroupWithRoles(c.instanceName, c.instanceGroup, []string{aem.RoleAuthor, aem.RolePublisher}) 50 | if err != nil { 51 | output.Printf(output.NORMAL, errorString, err.Error()) 52 | os.Exit(ExitError) 53 | } 54 | 55 | for _, i := range is { 56 | if project.Confirm(fmt.Sprintf("\U0001F198 Are you sure you want to delete %s? (This cannot be undone!!)", i.Name), "Deletion of instance confirmation.", c.force) { 57 | cPkgs := make([]*objects.Package, 0) 58 | if !c.backup { 59 | output.Printf(output.NORMAL, "\U0001F477 Creating package \n") 60 | now := time.Now() 61 | versionStr := fmt.Sprintf("%s.%d", now.Format("20060102"), now.UnixNano()) 62 | cPkg, err := pkg.Create(i, config.ContentBackupName, config.ContentBackupGroup, versionStr, config.ContentBackupPaths, false) 63 | cPkgs = append(cPkgs, cPkg) 64 | if err != nil { 65 | output.Printf(output.NORMAL, "Unable to build package. %s", err.Error()) 66 | os.Exit(ExitError) 67 | } 68 | output.Printf(output.NORMAL, "\U0001F525 Building package %s\n", cPkg.Name) 69 | pkg.Rebuild(&i, cPkg) 70 | pkg.AwaitBuild(&i, cPkg) 71 | output.Printf(output.NORMAL, "\U0001F64C Downloading package %s\n", cPkg.Name) 72 | pkg.Download(&i, cPkg) 73 | } 74 | 75 | aem.Destroy(i, c.force, *config) 76 | if c.create { 77 | aem.FullStart(i, true, true, false, config, cPkgs) 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /internal/aem/objects/instance.go: -------------------------------------------------------------------------------- 1 | package objects 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jlentink/aem/internal/output" 6 | "github.com/jlentink/aem/internal/sliceutil" 7 | "github.com/zalando/go-keyring" 8 | ) 9 | 10 | // Instance for aem instance 11 | type Instance struct { 12 | Name string `toml:"name"` 13 | Aliases []string `toml:"aliases"` 14 | Group string `toml:"group"` 15 | Debug bool `toml:"debug"` 16 | Protocol string `toml:"proto"` 17 | IP string `toml:"ip"` 18 | Hostname string `toml:"hostname"` 19 | Port int `toml:"port"` 20 | SPort int `toml:"secure-port"` 21 | DispatcherVersion string `toml:"dispatcher-version"` 22 | DispatcherEndpoint string `toml:"dispatcher-endpoint"` 23 | Author string `toml:"author"` 24 | Publisher string `toml:"publisher"` 25 | Type string `toml:"type"` 26 | RunMode string `toml:"runmode"` 27 | Username string `toml:"username"` 28 | SSHUsername string `toml:"ssh-username"` 29 | Password string `toml:"password"` 30 | JVMOptions []string `toml:"jvm-options"` 31 | JVMDebugOptions []string `toml:"jvm-debug-options"` 32 | Version string `toml:"version"` 33 | } 34 | 35 | // URLString for instance 36 | func (i *Instance) URLString() string { 37 | return fmt.Sprintf("%s://%s:%d", i.Protocol, i.Hostname, i.Port) 38 | } 39 | 40 | // URLIPString for instance 41 | func (i *Instance) URLIPString() string { 42 | return fmt.Sprintf("%s://%s:%d", i.Protocol, i.IP, i.Port) 43 | } 44 | 45 | // GetPassword get password for instance 46 | func (i *Instance) GetPassword() (string, error) { 47 | if Cnf.KeyRing { 48 | return keyring.Get(i.hostServiceName(), i.Username) 49 | } 50 | return i.Password, nil 51 | } 52 | 53 | // GetPasswordSimple Get password and not receive an error 54 | func (i *Instance) GetPasswordSimple() string { 55 | var passwd string 56 | var err error 57 | if Cnf.KeyRing { 58 | passwd, err = keyring.Get(i.hostServiceName(), i.Username) 59 | if err != nil { 60 | output.Print(output.VERBOSE, "Failed retrieving password from keychain dropping back to") 61 | passwd = i.Password 62 | } 63 | } 64 | if passwd == "" { 65 | passwd = i.Password 66 | } 67 | return passwd 68 | } 69 | 70 | // SetPassword set password for instance 71 | func (i *Instance) SetPassword(p string) error { 72 | return keyring.Set(i.hostServiceName(), i.Username, p) 73 | } 74 | 75 | func (i *Instance) hostServiceName() string { 76 | return fmt.Sprintf("%s-%s-%s-%s-%d", serviceName, Cnf.ProjectName, i.Name, i.Hostname, i.Port) 77 | } 78 | 79 | // GetVersion Get version for instance 80 | func (i *Instance) GetVersion() string { 81 | if len(i.Version) > 0 { 82 | return i.Version 83 | } 84 | return Cnf.Version 85 | } 86 | 87 | // InstanceOf is instance of defined groups 88 | func (i *Instance) InstanceOf(types []string) bool { 89 | if sliceutil.InSliceString(types, i.Type) { 90 | return true 91 | } 92 | return false 93 | } 94 | -------------------------------------------------------------------------------- /internal/aem/Password_test.go: -------------------------------------------------------------------------------- 1 | package aem 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/aem/objects" 5 | "runtime" 6 | "testing" 7 | ) 8 | 9 | func Test_serviceName(t *testing.T) { 10 | type args struct { 11 | i objects.Instance 12 | } 13 | tests := []struct { 14 | name string 15 | args args 16 | want string 17 | }{ 18 | { 19 | name: "Construct string", 20 | args: args{i: objects.Instance{Name: "Name", Hostname: "Hostname"}}, 21 | want: serviceNameID + "-Name-Hostname", 22 | }, 23 | } 24 | for _, tt := range tests { 25 | t.Run(tt.name, func(t *testing.T) { 26 | if got := serviceName(tt.args.i); got != tt.want { 27 | t.Errorf("serviceName() = %v, want %v", got, tt.want) 28 | } 29 | }) 30 | } 31 | } 32 | 33 | func TestKeyRingSetPassword(t *testing.T) { 34 | type args struct { 35 | i objects.Instance 36 | password string 37 | } 38 | tests := []struct { 39 | name string 40 | args args 41 | wantErr bool 42 | }{ 43 | { 44 | name: "Get password", 45 | args: args{i: objects.Instance{Name: "Name", Hostname: "Hostname"}, password: "password"}, 46 | wantErr: false, 47 | }, 48 | } 49 | for _, tt := range tests { 50 | t.Run(tt.name, func(t *testing.T) { 51 | if runtime.GOOS == "darwin" { 52 | if err := KeyRingSetPassword(tt.args.i, tt.args.password); (err != nil) != tt.wantErr { 53 | t.Errorf("KeyRingSetPassword() error = %v, wantErr %v", err, tt.wantErr) 54 | } 55 | 56 | password, _ := KeyRingGetPassword(tt.args.i) 57 | if tt.args.password != password { 58 | t.Errorf("KeyRingSetPassword() error = %s, want %s", password, tt.args.password) 59 | } 60 | 61 | } else { 62 | t.Logf("Wrong os skipping. %s", runtime.GOOS) 63 | } 64 | }) 65 | } 66 | } 67 | 68 | func TestKeyRingGetPassword(t *testing.T) { 69 | type args struct { 70 | i objects.Instance 71 | } 72 | tests := []struct { 73 | name string 74 | args args 75 | want string 76 | wantErr bool 77 | }{ 78 | // TODO: Add test cases. 79 | } 80 | for _, tt := range tests { 81 | t.Run(tt.name, func(t *testing.T) { 82 | got, err := KeyRingGetPassword(tt.args.i) 83 | if (err != nil) != tt.wantErr { 84 | t.Errorf("KeyRingGetPassword() error = %v, wantErr %v", err, tt.wantErr) 85 | return 86 | } 87 | if got != tt.want { 88 | t.Errorf("KeyRingGetPassword() = %v, want %v", got, tt.want) 89 | } 90 | }) 91 | } 92 | } 93 | 94 | func TestGetPasswordForInstance(t *testing.T) { 95 | type args struct { 96 | i objects.Instance 97 | useKeyring bool 98 | } 99 | tests := []struct { 100 | name string 101 | args args 102 | want string 103 | wantErr bool 104 | }{ 105 | // TODO: Add test cases. 106 | } 107 | for _, tt := range tests { 108 | t.Run(tt.name, func(t *testing.T) { 109 | got, err := GetPasswordForInstance(tt.args.i, tt.args.useKeyring) 110 | if (err != nil) != tt.wantErr { 111 | t.Errorf("GetPasswordForInstance() error = %v, wantErr %v", err, tt.wantErr) 112 | return 113 | } 114 | if got != tt.want { 115 | t.Errorf("GetPasswordForInstance() = %v, want %v", got, tt.want) 116 | } 117 | }) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /internal/http/Download.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/dustin/go-humanize" 7 | "github.com/go-http-utils/headers" 8 | "github.com/jlentink/aem/internal/cli/project" 9 | "github.com/jlentink/aem/internal/output" 10 | "github.com/jlentink/aem/internal/version" 11 | "io" 12 | "net/http" 13 | "net/url" 14 | "strconv" 15 | ) 16 | 17 | func downloadSize(req *http.Request) (uint64, error) { 18 | method := req.Method 19 | req.Method = http.MethodHead 20 | 21 | client := &http.Client{} 22 | resp, err := client.Do(req) 23 | if err != nil { 24 | output.Print(output.VERBOSE, "unable to create http client\n") 25 | return 0, errors.New("unable to create http client") 26 | } 27 | defer resp.Body.Close() // nolint: errcheck 28 | 29 | if resp.StatusCode != http.StatusOK { 30 | req.Method = method 31 | output.Printf(output.VERBOSE, "received wrong http status %d", resp.StatusCode) 32 | return 0, fmt.Errorf("received wrong http status %d", resp.StatusCode) 33 | } 34 | 35 | size, err := strconv.Atoi(resp.Header.Get(headers.ContentLength)) 36 | if err != nil { 37 | req.Method = method 38 | return 0, fmt.Errorf("could not create int from response %s", resp.Header.Get(headers.ContentLength)) 39 | } 40 | output.Printf(output.VERBOSE, "Download size found: %d bytes\n", size) 41 | req.Method = method 42 | return uint64(size), nil 43 | } 44 | 45 | // DownloadFileWithURL file with url.URL 46 | func DownloadFileWithURL(destination string, uri *url.URL, forceDownload bool) (uint64, error) { 47 | if project.Exists(destination) && !forceDownload { 48 | return 0, nil 49 | } 50 | 51 | req, _ := http.NewRequest(http.MethodGet, "", nil) 52 | req.Header.Add(headers.CacheControl, configNoCache) 53 | req.Header.Add(headers.UserAgent, "aemCLI - "+version.GetVersion()) 54 | req.URL = uri 55 | 56 | fs, _ := downloadSize(req) 57 | client := &http.Client{} 58 | resp, err := client.Do(req) 59 | if err != nil { 60 | return 0, err 61 | } 62 | defer resp.Body.Close() // nolint: errcheck 63 | 64 | fmt.Printf("Downloading: %s (%s)\n", uri.Path, humanize.Bytes(fs)) 65 | counter := &progressReporter{r: resp.Body, totalSize: fs, label: "Downloading"} 66 | out, err := project.Create(destination + ".tmp") 67 | if err != nil { 68 | output.Printf(output.VERBOSE, "unable to create tmp file %s", destination+".tmp") 69 | return 0, err 70 | } 71 | 72 | _, err = io.Copy(out, counter) 73 | if err != nil { 74 | return 0, err 75 | } 76 | 77 | err = out.Close() 78 | if err != nil { 79 | return 0, err 80 | } 81 | err = project.Rename(destination+".tmp", destination) 82 | if err != nil { 83 | fmt.Print("\n") 84 | return 0, err 85 | } 86 | 87 | fmt.Print("\n") 88 | return fs, nil 89 | } 90 | 91 | // DownloadFile download file from string url 92 | func DownloadFile(destination string, uri string, username string, password string, forceDownload bool) (uint64, error) { 93 | URL, err := url.Parse(uri) 94 | if err != nil { 95 | return 0, err 96 | } 97 | 98 | if username != "" || password != "" { 99 | URL.User = url.UserPassword(username, password) 100 | 101 | } 102 | return DownloadFileWithURL(destination, URL, forceDownload) 103 | } 104 | -------------------------------------------------------------------------------- /internal/aem/objects/config_test.go: -------------------------------------------------------------------------------- 1 | package objects 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/cli/project" 5 | "testing" 6 | ) 7 | 8 | func TestExists(t *testing.T) { 9 | tests := []struct { 10 | name string 11 | want bool 12 | wantFile bool 13 | }{ 14 | { 15 | name: "Test if config exists (not exists)", 16 | want: false, 17 | wantFile: false, 18 | }, 19 | { 20 | name: "Test if config exists (exists)", 21 | want: true, 22 | wantFile: true, 23 | }, 24 | } 25 | for _, tt := range tests { 26 | t.Run(tt.name, func(t *testing.T) { 27 | project.Mock(false) 28 | if tt.wantFile { 29 | cf, _ := project.GetConfigFileLocation() 30 | project.WriteTextFile(cf, "123") 31 | } 32 | 33 | if got := Exists(); got != tt.want { 34 | t.Errorf("ConfigExists() = %v, want %v", got, tt.want) 35 | } 36 | }) 37 | } 38 | } 39 | 40 | func TestJoinStrings(t *testing.T) { 41 | type args struct { 42 | s []string 43 | } 44 | 45 | tests := []struct { 46 | name string 47 | want string 48 | args args 49 | }{ 50 | { 51 | name: "Empty slice", 52 | want: "", 53 | args: args{s: []string{}}, 54 | }, 55 | { 56 | name: "single element slice", 57 | want: "\t\"1\",\n", 58 | args: args{s: []string{"1"}}, 59 | }, 60 | { 61 | name: "two element slice", 62 | want: "\t\"1\",\n\t\"2\",\n", 63 | args: args{s: []string{"1", "2"}}, 64 | }, 65 | } 66 | for _, tt := range tests { 67 | t.Run(tt.name, func(t *testing.T) { 68 | got := joinStrings(tt.args.s) 69 | if got != tt.want { 70 | t.Errorf("joinStrings() = %v, want %v", got, tt.want) 71 | } 72 | }) 73 | } 74 | } 75 | 76 | func TestRender(t *testing.T) { 77 | tests := []struct { 78 | name string 79 | want int 80 | }{ 81 | { 82 | name: "Render template", 83 | want: 2914, 84 | }, 85 | } 86 | for _, tt := range tests { 87 | t.Run(tt.name, func(t *testing.T) { 88 | got := Render() 89 | if len(got) != tt.want { 90 | t.Errorf("Render() = %v, want %v", len(got), tt.want) 91 | } 92 | }) 93 | } 94 | } 95 | 96 | func TestWriteConfigFile(t *testing.T) { 97 | tests := []struct { 98 | name string 99 | want int 100 | wantErr bool 101 | wantRO bool 102 | wantFsErr bool 103 | }{ 104 | { 105 | name: "Write template", 106 | want: 2914, 107 | wantErr: false, 108 | wantRO: false, 109 | wantFsErr: false, 110 | }, 111 | { 112 | name: "Write template error", 113 | want: 0, 114 | wantErr: true, 115 | wantRO: true, 116 | wantFsErr: false, 117 | }, 118 | { 119 | name: "Write template error", 120 | want: 0, 121 | wantErr: true, 122 | wantRO: true, 123 | wantFsErr: true, 124 | }, 125 | } 126 | for _, tt := range tests { 127 | t.Run(tt.name, func(t *testing.T) { 128 | project.Mock(tt.wantRO) 129 | project.FilesystemError(tt.wantFsErr) 130 | written, err := WriteConfigFile() 131 | 132 | if (err != nil) != tt.wantErr { 133 | t.Errorf("Structure.writeTextFile() error = %v, wantErr %v", err, tt.wantErr) 134 | return 135 | } 136 | 137 | if written != tt.want { 138 | t.Errorf("Render() = %v, want %v", written, tt.want) 139 | } 140 | }) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /internal/commands/reindex.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jlentink/aem/internal/aem" 6 | "github.com/jlentink/aem/internal/aem/indexes" 7 | "github.com/jlentink/aem/internal/output" 8 | "github.com/manifoldco/promptui" 9 | "github.com/spf13/cobra" 10 | "os" 11 | "strings" 12 | ) 13 | 14 | type commandReindex struct { 15 | verbose bool 16 | instanceName string 17 | index string 18 | } 19 | 20 | func (c *commandReindex) setup() *cobra.Command { 21 | cmd := &cobra.Command{ 22 | Use: "index", 23 | Short: "Reindex index on instance", 24 | Aliases: []string{"reindex"}, 25 | PreRun: c.preRun, 26 | Run: c.run, 27 | } 28 | cmd.Flags().StringVarP(&c.instanceName, "name", "n", aem.GetDefaultInstanceName(), "Instance to stop") 29 | cmd.Flags().StringVarP(&c.index, "index", "i", "", "Index to reindex") 30 | return cmd 31 | } 32 | 33 | func (c *commandReindex) preRun(cmd *cobra.Command, args []string) { 34 | c.verbose, _ = cmd.Flags().GetBool("verbose") 35 | output.SetVerbose(verbose) 36 | 37 | ConfigCheckListProjects() 38 | RegisterProject() 39 | } 40 | 41 | func (c *commandReindex) run(cmd *cobra.Command, args []string) { 42 | _, i, errorString, err := getConfigAndInstance(c.instanceName) 43 | if err != nil { 44 | output.Printf(output.NORMAL, errorString, err.Error()) 45 | os.Exit(ExitError) 46 | } 47 | 48 | if c.index == "" { 49 | searchIndexes, indexErr := indexes.GetIndexes(i) 50 | if indexErr != nil { 51 | output.Printf(output.NORMAL, "Error retrieving indexes. (%s)", err.Error()) 52 | os.Exit(ExitError) 53 | } 54 | index := c.searchIndex(searchIndexes) 55 | if index == nil { 56 | output.Printf(output.NORMAL, "Process killed stopping...") 57 | os.Exit(ExitError) 58 | 59 | } 60 | c.index = index.Name 61 | } 62 | 63 | err = indexes.Reindex(i, c.index) 64 | if err != nil { 65 | output.Printf(output.NORMAL, "Error sending reindex. (%s)", err) 66 | os.Exit(ExitError) 67 | } 68 | 69 | } 70 | 71 | func (c *commandReindex) searchIndex(indexesList []*indexes.Index) *indexes.Index { 72 | 73 | localIndexes := make([]indexes.Index, 0) 74 | for _, cIndex := range indexesList { 75 | localIndexes = append(localIndexes, *cIndex) 76 | } 77 | 78 | templates := &promptui.SelectTemplates{ 79 | Label: "{{ . }}?", 80 | Active: "\U000027A1 {{ .Name | cyan }} ({{ .Info | red }})", 81 | Inactive: " {{ .Name | cyan }} ({{ .Info | red }})", 82 | Selected: "\U0001F522 ReIndexing... {{ .Name | red | cyan }}", 83 | Details: `--------- Package ---------- 84 | {{ "Name:" | faint }} {{ .Name }} 85 | {{ "Reindex count:" | faint }} {{ .ReindexCount }} 86 | {{ "Info:" | faint }} {{ .Info }} 87 | `, 88 | } 89 | 90 | searcher := func(input string, index int) bool { 91 | cIndex := indexesList[index] 92 | name := strings.Replace(strings.ToLower(cIndex.Name), " ", "", -1) 93 | input = strings.Replace(strings.ToLower(input), " ", "", -1) 94 | return strings.Contains(name, input) 95 | } 96 | 97 | prompt := promptui.Select{ 98 | Label: "Select Index", 99 | Items: localIndexes, 100 | Templates: templates, 101 | Size: 20, 102 | Searcher: searcher, 103 | } 104 | 105 | in, _, err := prompt.Run() 106 | 107 | if err != nil { 108 | fmt.Printf("Prompt failed %v\n", err) 109 | return nil 110 | } 111 | 112 | return &localIndexes[in] 113 | 114 | } 115 | -------------------------------------------------------------------------------- /internal/aem/http_test.go: -------------------------------------------------------------------------------- 1 | package aem 2 | 3 | import ( 4 | "github.com/jlentink/aem/internal/aem/objects" 5 | "runtime" 6 | "testing" 7 | ) 8 | 9 | func TestURL(t *testing.T) { 10 | type args struct { 11 | i objects.Instance 12 | } 13 | tests := []struct { 14 | name string 15 | args args 16 | want string 17 | }{ 18 | { 19 | name: "Test http", 20 | args: args{i: objects.Instance{ 21 | Protocol: "http", 22 | Port: 4502, 23 | Hostname: "some-domain", 24 | Username: "username", 25 | Password: "password", 26 | }}, 27 | want: "http://some-domain:4502", 28 | }, 29 | { 30 | name: "Test https", 31 | args: args{i: objects.Instance{ 32 | Protocol: "https", 33 | Port: 4503, 34 | Hostname: "some-domain", 35 | Username: "username", 36 | Password: "password", 37 | }}, 38 | want: "https://some-domain:4503", 39 | }, 40 | } 41 | for _, tt := range tests { 42 | t.Run(tt.name, func(t *testing.T) { 43 | if got := URLString(&tt.args.i); got != tt.want { 44 | t.Errorf("URLString() = %v, want %v", got, tt.want) 45 | } 46 | }) 47 | } 48 | } 49 | 50 | func TestPasswordURL(t *testing.T) { 51 | type args struct { 52 | i objects.Instance 53 | useKeyring bool 54 | } 55 | tests := []struct { 56 | name string 57 | args args 58 | want string 59 | wantErr bool 60 | }{ 61 | { 62 | name: "Test https", 63 | args: args{i: objects.Instance{ 64 | Name: "some-host", 65 | Protocol: "https", 66 | Port: 4503, 67 | Hostname: "some-domain", 68 | Username: "username", 69 | Password: "password", 70 | }, useKeyring: false}, 71 | want: "https://username:password@some-domain:4503", 72 | }, 73 | { 74 | name: "Test escape", 75 | args: args{i: objects.Instance{ 76 | Name: "some-host", 77 | Protocol: "https", 78 | Port: 4503, 79 | Hostname: "some-domain", 80 | Username: "user:name", 81 | Password: "pass@word", 82 | }, useKeyring: false}, 83 | want: "https://user%3Aname:pass%40word@some-domain:4503", 84 | }, 85 | { 86 | name: "Test https", 87 | args: args{i: objects.Instance{ 88 | Name: "some-host", 89 | Protocol: "https", 90 | Port: 4503, 91 | Hostname: "some-domain", 92 | Username: "username", 93 | Password: "password", 94 | }, useKeyring: true}, 95 | want: "https://username:password@some-domain:4503", 96 | }, 97 | { 98 | name: "Test escape", 99 | args: args{i: objects.Instance{ 100 | Name: "some-host", 101 | Protocol: "https", 102 | Port: 4503, 103 | Hostname: "some-domain", 104 | Username: "user:name", 105 | Password: "pass@word", 106 | }, useKeyring: true}, 107 | want: "https://user%3Aname:pass%40word@some-domain:4503", 108 | }, 109 | } 110 | for _, tt := range tests { 111 | t.Run(tt.name, func(t *testing.T) { 112 | if runtime.GOOS != "darwin" { 113 | t.Logf("detected non-darwin (%s) disabling keychain test", runtime.GOOS) 114 | tt.args.useKeyring = false 115 | } else { 116 | if tt.args.useKeyring { 117 | KeyRingSetPassword(tt.args.i, tt.args.i.Password) 118 | } 119 | } 120 | got, err := PasswordURL(tt.args.i, tt.args.useKeyring) 121 | if (err != nil) != tt.wantErr { 122 | t.Errorf("PasswordURL() error = %v, wantErr %v", err, tt.wantErr) 123 | return 124 | } 125 | if got != tt.want { 126 | t.Errorf("PasswordURL() = %v, want %v", got, tt.want) 127 | } 128 | }) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at jason@mediamonks.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /internal/commands/packageDownload.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jlentink/aem/internal/aem" 6 | "github.com/jlentink/aem/internal/aem/objects" 7 | "github.com/jlentink/aem/internal/aem/pkg" 8 | "github.com/jlentink/aem/internal/output" 9 | "github.com/manifoldco/promptui" 10 | "github.com/spf13/cobra" 11 | "os" 12 | "strings" 13 | ) 14 | 15 | type commandPackageDownload struct { 16 | verbose bool 17 | instanceName string 18 | packageName string 19 | } 20 | 21 | func (c *commandPackageDownload) setup() *cobra.Command { 22 | cmd := &cobra.Command{ 23 | Use: "download", 24 | Short: "Download packages", 25 | Aliases: []string{"down"}, 26 | PreRun: c.preRun, 27 | Run: c.run, 28 | } 29 | cmd.Flags().StringVarP(&c.instanceName, "name", "n", aem.GetDefaultInstanceName(), "Instance to stop") 30 | cmd.Flags().StringVarP(&c.packageName, "package", "p", ``, "Package name. E.g: name, name:1.0.0") 31 | cmd.MarkFlagRequired("name") // nolint: errcheck 32 | return cmd 33 | } 34 | 35 | func (c *commandPackageDownload) preRun(cmd *cobra.Command, args []string) { 36 | c.verbose, _ = cmd.Flags().GetBool("verbose") 37 | output.SetVerbose(c.verbose) 38 | 39 | ConfigCheckListProjects() 40 | RegisterProject() 41 | } 42 | 43 | func (c *commandPackageDownload) run(cmd *cobra.Command, args []string) { 44 | _, i, errorString, err := getConfigAndInstance(c.instanceName) 45 | if err != nil { 46 | output.Printf(output.NORMAL, errorString, err.Error()) 47 | os.Exit(ExitError) 48 | } 49 | 50 | if len(c.packageName) > 0 { 51 | c.downloadByName(i) 52 | } else { 53 | err := c.downloadSearch(i) 54 | if err != nil { 55 | output.Printf(output.NORMAL, "Could not download package. %s", err.Error()) 56 | os.Exit(ExitError) 57 | } 58 | } 59 | } 60 | 61 | func (c *commandPackageDownload) downloadByName(i *objects.Instance) { 62 | _, err := pkg.DownloadWithName(i, c.packageName) 63 | 64 | if err != nil { 65 | output.Printf(output.NORMAL, "Could not download package. %s", err.Error()) 66 | os.Exit(ExitError) 67 | } 68 | } 69 | 70 | func (c *commandPackageDownload) downloadSearch(i *objects.Instance) error { 71 | pkgs, err := pkg.PackageList(*i) 72 | if err != nil { 73 | output.Printf(output.NORMAL, "Could not retrieve list from server %s", err.Error()) 74 | os.Exit(ExitError) 75 | } 76 | 77 | templates := &promptui.SelectTemplates{ 78 | Label: "{{ . }}?", 79 | Active: "\U000027A1 {{ .Name | cyan }} ({{ .Version | red }})", 80 | Inactive: " {{ .Name | cyan }} ({{ .Version | red }})", 81 | Selected: "\U00002705 Downloading... {{ .Name | red | cyan }}", 82 | Details: `--------- Package ---------- 83 | {{ "Name:" | faint }} {{ .Name }} 84 | {{ "Version:" | faint }} {{ .Version }} 85 | {{ "Size:" | faint }} {{ .SizeHuman }} 86 | {{ "Created:" | faint }} {{ .CreatedStr }} 87 | {{ "Modified:" | faint }} {{ .LastModifiedByStr }} 88 | `, 89 | } 90 | 91 | searcher := func(input string, index int) bool { 92 | pkg := pkgs[index] 93 | name := strings.Replace(strings.ToLower(pkg.Name), " ", "", -1) 94 | input = strings.Replace(strings.ToLower(input), " ", "", -1) 95 | return strings.Contains(name, input) 96 | } 97 | 98 | prompt := promptui.Select{ 99 | Label: "Select package:", 100 | Items: pkgs, 101 | Templates: templates, 102 | Size: 20, 103 | Searcher: searcher, 104 | } 105 | 106 | in, _, err := prompt.Run() 107 | 108 | if err != nil { 109 | fmt.Printf("Prompt failed %v\n", err) 110 | return err 111 | } 112 | 113 | _, err = pkg.Download(i, &pkgs[in]) 114 | return err 115 | } 116 | --------------------------------------------------------------------------------