diff --git a/README.md b/README.md index 3049decc..642e7f3b 100644 --- a/README.md +++ b/README.md @@ -138,31 +138,29 @@ Supported project types: --- -Run command below to initialize the AEM Compose tool in your project: +Run command below to install the AEM Compose tool in your project: ```shell -curl https://raw.githubusercontent.com/wttech/aemc/main/project-init.sh | sh +curl https://raw.githubusercontent.com/wttech/aemc/main/project-install.sh | sh ``` -and then: - -```shell -sh aemw init -``` - -After successful initialization, remember to always use the tool via wrapper script in the following way: +After successful installation, remember to always use the tool via wrapper script in the following way: ```shell sh aemw [command] ``` -For example: +Next scaffold the AEM Compose files in the project: ```shell -sh aemw version +sh aemw project scaffold ``` -Project initialization sets up ready-to-use tasks powered by [Task tool](https://taskfile.dev/) which are aggregating one or many AEM Compose CLI commands into useful procedures. +Project scaffolding: + +- sets up ready-to-use tasks powered by [Task tool](https://taskfile.dev/), which aggregate one or many AEM Compose CLI commands into useful procedures. +- provides configuration for provisioning AEM instances (installing service pack, setting replication agents, etc.). +- provides configuration for running AEM Dispatcher on [Podman](https://podman-desktop.io/) or [Docker](https://www.docker.com/products/docker-desktop/). To list all available tasks, run: @@ -176,19 +174,6 @@ For example: sh taskw setup ``` -Some tasks like `aem:build` may accept parameters. -For example, to build AEM application with: - -- Applying frontend development mode Maven profile -- Unit tests skipping -- UI tests skipping - -Simply run command with appending [task variable](https://taskfile.dev/usage/#variables) to the end: - -```shell -sh taskw aem:build AEM_BUILD_ARGS="-PfedDev -DskipTests -pl '!ui.tests'" -``` - ## IaaC Providers The tool is designed to be used in Infrastructure as a Code (IaaC) solutions such as [Terraform](https://www.terraform.io/) or [Pulumi](https://www.pulumi.com/). @@ -378,7 +363,7 @@ instance: # Oak Run tool options (offline instance management) oak_run: - download_url: "https://repo1.maven.org/maven2/org/apache/jackrabbit/oak-run/1.44.0/oak-run-1.44.0.jar" + download_url: "https://repo1.maven.org/maven2/org/apache/jackrabbit/oak-run/1.72.0/oak-run-1.72.0.jar" store_path: "crx-quickstart/repository/segmentstore" # Source files @@ -458,7 +443,7 @@ java: # Auto-installed JDK options download: # Source URL with template vars support - url: "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.18%2B10/OpenJDK11U-jdk_[[.Arch]]_[[.Os]]_hotspot_11.0.18_10.[[.ArchiveExt]]" + url: "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.25%2B9/OpenJDK11U-jdk_[[.Arch]]_[[.Os]]_hotspot_11.0.25_9.[[.ArchiveExt]]" # Map source URL template vars to be compatible with Adoptium Java replacements: # Var 'Os' (GOOS) @@ -471,9 +456,6 @@ java: "arm64": "x64" "aarch64": "x64" -vault: - "download_url": "https://repo1.maven.org/maven2/org/apache/jackrabbit/vault/vault-cli/3.7.2/vault-cli-3.7.2-bin.tar.gz" - base: # Location of temporary files (downloaded AEM packages, etc) tmp_dir: aem/home/tmp diff --git a/cmd/aem/crypto.go b/cmd/aem/crypto.go index 918f6ebf..1e9e0d38 100644 --- a/cmd/aem/crypto.go +++ b/cmd/aem/crypto.go @@ -3,7 +3,6 @@ package main import ( "github.com/spf13/cobra" "github.com/wttech/aemc/pkg" - "github.com/wttech/aemc/pkg/common" "github.com/wttech/aemc/pkg/common/mapsx" ) @@ -59,8 +58,9 @@ func (c *CLI) cryptoSetupCmd() *cobra.Command { } }, } - cmd.Flags().String("hmac-file", common.LibDir+"/crypto/data/hmac", "Path to file 'hmac'") - cmd.Flags().String("master-file", common.LibDir+"/crypto/data/master", "Path to file 'master'") + libDir := c.config.Values().GetString("base.lib_dir") + cmd.Flags().String("hmac-file", libDir+"/crypto/data/hmac", "Path to file 'hmac'") + cmd.Flags().String("master-file", libDir+"/crypto/data/master", "Path to file 'master'") return cmd } diff --git a/cmd/aem/instance.go b/cmd/aem/instance.go index 1b004679..5c1cf37b 100644 --- a/cmd/aem/instance.go +++ b/cmd/aem/instance.go @@ -23,7 +23,6 @@ func (c *CLI) instanceCmd() *cobra.Command { cmd.AddCommand(c.instanceListCmd()) cmd.AddCommand(c.instanceAwaitCmd()) cmd.AddCommand(c.instanceBackupCmd()) - cmd.AddCommand(c.instanceInitCmd()) cmd.AddCommand(c.instanceImportCmd()) return cmd } @@ -334,34 +333,3 @@ func (c *CLI) instanceListCmd() *cobra.Command { }, } } - -func (c *CLI) instanceInitCmd() *cobra.Command { - return &cobra.Command{ - Use: "init", - Aliases: []string{"initialize"}, - Short: "Init prerequisites for AEM instance(s)", - Run: func(cmd *cobra.Command, args []string) { - if err := c.aem.InstanceManager().LocalOpts.Initialize(); err != nil { - c.Error(err) - return - } - - javaHome, err := c.aem.JavaOpts().FindHomeDir() - if err != nil { - c.Error(err) - return - } - c.SetOutput("javaHome", javaHome) - - javaExecutable, err := c.aem.JavaOpts().Executable() - if err != nil { - c.Error(err) - return - } - c.SetOutput("javaExecutable", javaExecutable) - - c.SetOutput("initialized", true) - c.Changed("initialized prerequisites for instance(s)") - }, - } -} diff --git a/cmd/aem/project.go b/cmd/aem/project.go index 32031127..8a702a03 100644 --- a/cmd/aem/project.go +++ b/cmd/aem/project.go @@ -7,18 +7,26 @@ import ( "strings" ) +func (c *CLI) projectCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "project", + Short: "Manage project files", + Aliases: []string{"prj"}, + } + cmd.AddCommand(c.projectInitCmd()) + cmd.AddCommand(c.projectScaffoldCmd()) + + return cmd +} + const projectKindFlag = "project-kind" -func (c *CLI) initCmd() *cobra.Command { +func (c *CLI) projectScaffoldCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "init", - Aliases: []string{"initialize"}, - Short: "Initializes AEMC in the project", + Use: "scaffold", + Aliases: []string{"setup"}, + Short: "Scaffold required files in the project", Run: func(cmd *cobra.Command, args []string) { - if err := c.aem.Project().EnsureDirs(); err != nil { - c.Error(err) - return - } kindName, _ := cmd.Flags().GetString(projectKindFlag) kind, err := c.aem.Project().KindDetermine(kindName) if err != nil { @@ -29,17 +37,66 @@ func (c *CLI) initCmd() *cobra.Command { c.Fail(fmt.Sprintf("project kind cannot be determined; specify it with flag '--%s=[%s]'", projectKindFlag, strings.Join(project.KindStrings(), "|"))) return } - changed, err := c.aem.Project().InitializeWithChanged(kind) + + changed, err := c.aem.Project().ScaffoldWithChanged(kind) + if err != nil { + c.Error(err) + return + } + + c.SetOutput("gettingStarted", c.aem.Project().ScaffoldGettingStarted()) + + if changed { + c.Changed("project files scaffolded") + } else { + c.Ok("project files already scaffolded") + } + }, + } + cmd.Flags().String(projectKindFlag, project.KindAuto, fmt.Sprintf("Type of AEM to work with (%s)", strings.Join(project.KindStrings(), "|"))) + return cmd +} + +func (c *CLI) projectInitCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "init", + Aliases: []string{"initialize"}, + Short: "Initializes AEMC in the project", + Run: func(cmd *cobra.Command, args []string) { + if !c.aem.Project().IsScaffolded() { + c.Fail(fmt.Sprintf("project need to be scaffolded before running initialization")) + return + } + + changed := false + + c.SetOutput("gettingStarted", c.aem.Project().InitGettingStartedError()) + + baseChanged, err := c.aem.BaseOpts().PrepareWithChanged() + changed = changed || baseChanged if err != nil { c.Error(err) return } - gettingStarted, err := c.aem.Project().GettingStarted(kind) + c.SetOutput("baseChanged", baseChanged) + + // Download and prepare vendor tools (including JDK and AEM SDK) + vendorPrepared, err := c.aem.VendorManager().PrepareWithChanged() + changed = changed || vendorPrepared if err != nil { c.Error(err) return } - c.SetOutput("gettingStarted", gettingStarted) + c.SetOutput("vendorPrepared", vendorPrepared) + + // Validate AEM instance files and prepared SDK + if err := c.aem.InstanceManager().LocalOpts.Validate(); err != nil { + c.Error(err) + return + } + + c.SetOutput("gettingStarted", c.aem.Project().InitGettingStartedSuccess()) + if changed { c.Changed("project initialized") } else { @@ -47,6 +104,5 @@ func (c *CLI) initCmd() *cobra.Command { } }, } - cmd.Flags().String(projectKindFlag, project.KindAuto, fmt.Sprintf("Type of AEM to work with (%s)", strings.Join(project.KindStrings(), "|"))) return cmd } diff --git a/cmd/aem/root.go b/cmd/aem/root.go index 2be27b48..2149ae1f 100644 --- a/cmd/aem/root.go +++ b/cmd/aem/root.go @@ -19,7 +19,8 @@ func (c *CLI) rootCmd() *cobra.Command { }, } cmd.AddCommand(c.versionCmd()) - cmd.AddCommand(c.initCmd()) + cmd.AddCommand(c.projectCmd()) + cmd.AddCommand(c.vendorCmd()) cmd.AddCommand(c.configCmd()) cmd.AddCommand(c.instanceCmd()) cmd.AddCommand(c.osgiCmd()) @@ -33,8 +34,9 @@ func (c *CLI) rootCmd() *cobra.Command { cmd.AddCommand(c.fileCmd()) cmd.AddCommand(c.authCmd()) cmd.AddCommand(c.contentCmd()) - cmd.AddCommand(c.vaultCmd()) + c.rootFlags(cmd) + return cmd } diff --git a/cmd/aem/vault.go b/cmd/aem/vault.go deleted file mode 100644 index f37b4705..00000000 --- a/cmd/aem/vault.go +++ /dev/null @@ -1,30 +0,0 @@ -package main - -import ( - "github.com/spf13/cobra" - "github.com/wttech/aemc/pkg" -) - -func (c *CLI) vaultCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "vlt", - Short: "Executes Vault commands", - Run: func(cmd *cobra.Command, args []string) { - vaultCli := pkg.NewVaultCli(c.aem) - if err := vaultCli.CommandShell(args); err != nil { - c.Error(err) - return - } - }, - Args: cobra.ArbitraryArgs, - FParseErrWhitelist: cobra.FParseErrWhitelist{ - UnknownFlags: true, - }, - } - cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) { - aem := pkg.NewAEM(c.config) - vaultCli := pkg.NewVaultCli(aem) - _ = vaultCli.CommandShell(args) - }) - return cmd -} diff --git a/cmd/aem/vendor.go b/cmd/aem/vendor.go new file mode 100644 index 00000000..b4913e1d --- /dev/null +++ b/cmd/aem/vendor.go @@ -0,0 +1,76 @@ +package main + +import ( + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "os" +) + +func (c *CLI) vendorCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "vendor", + Short: "Supportive tools management", + Aliases: []string{"ven"}, + } + cmd.AddCommand(c.vendorListCmd()) + cmd.AddCommand(c.vendorPrepareCmd()) + + return cmd +} + +func (c *CLI) vendorListCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Short: "List vendor tools available", + Aliases: []string{"ls"}, + Run: func(cmd *cobra.Command, args []string) { + verbose, _ := cmd.Flags().GetBool("verbose") + + javaHome, err := c.aem.VendorManager().JavaManager().FindHomeDir() + if err != nil { + javaHome = os.Getenv("JAVA_HOME") + if verbose { + log.Warnf("java home not available: %s", err) + } + } + c.SetOutput("javaHome", javaHome) + + javaExecutable, err := c.aem.VendorManager().JavaManager().Executable() + if err != nil { + if verbose { + log.Warnf("java executable not available: %s", err) + } + } + c.SetOutput("javaExecutable", javaExecutable) + + oakRunJar := c.aem.VendorManager().OakRun().JarFile() + c.setOutput("oakRunJar", oakRunJar) + + c.Ok("vendor tools listed") + }, + } + cmd.Flags().BoolP("verbose", "v", false, "Log errors") + return cmd +} + +func (c *CLI) vendorPrepareCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "prepare", + Short: "Prepare vendor tools", + Aliases: []string{"prep", "download", "dw"}, + Run: func(cmd *cobra.Command, args []string) { + changed, err := c.aem.VendorManager().PrepareWithChanged() + if err != nil { + c.Error(err) + return + } + + if changed { + c.Changed("vendor tools prepared") + } else { + c.Ok("vendor tools already prepared") + } + }, + } + return cmd +} diff --git a/examples/docker/src/aem/default/etc/aem.yml b/examples/docker/src/aem/default/etc/aem.yml index cfbe20a2..f2301d1c 100755 --- a/examples/docker/src/aem/default/etc/aem.yml +++ b/examples/docker/src/aem/default/etc/aem.yml @@ -123,24 +123,11 @@ instance: local: # Wait only for those instances whose state has been changed internally (unaware of external changes) await_strict: true - # Current runtime dir (Sling launchpad, JCR repository) unpack_dir: "aem/home/var/instance" # Archived runtime dir (AEM backup files '*.aemb.zst') backup_dir: "aem/home/var/backup" - # Oak Run tool options (offline instance management) - oak_run: - download_url: "https://repo1.maven.org/maven2/org/apache/jackrabbit/oak-run/1.44.0/oak-run-1.44.0.jar" - store_path: "crx-quickstart/repository/segmentstore" - - # Source files - quickstart: - # AEM SDK ZIP or JAR - dist_file: "aem/home/lib/{aem-sdk,cq-quickstart}-*.{zip,jar}" - # AEM License properties file - license_file: "aem/home/lib/license.properties" - # Status discovery (timezone, AEM version, etc) status: timeout: 500ms @@ -219,35 +206,9 @@ instance: timeout: 10m delay: 10s -java: - # Require following versions before e.g running AEM instances - version_constraints: ">= 11, < 12" - - # Pre-installed local JDK dir - # a) keep it empty to download open source Java automatically for current OS and architecture - # b) set it to absolute path or to env var '[[.Env.JAVA_HOME]]' to indicate where closed source Java like Oracle is installed - home_dir: "" - - # Auto-installed JDK options - download: - # Source URL with template vars support - url: "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.18%2B10/OpenJDK11U-jdk_[[.Arch]]_[[.Os]]_hotspot_11.0.18_10.[[.ArchiveExt]]" - # Map source URL template vars to be compatible with Adoptium Java - replacements: - # Var 'Os' (GOOS) - "darwin": "mac" - # Var 'Arch' (GOARCH) - "x86_64": "x64" - "amd64": "x64" - "386": "x86-32" - # enforce non-ARM Java as some AEM features are not working on ARM (e.g Scene7) - "arm64": "x64" - "aarch64": "x64" - -vault: - "download_url": "https://repo1.maven.org/maven2/org/apache/jackrabbit/vault/vault-cli/3.7.2/vault-cli-3.7.2-bin.tar.gz" - base: + # Location of library files (AEM SDK ZIP, Quickstart JAR & License, Crypto keys, service packs, additional packages, etc.) + lib_dir: aem/home/lib # Location of temporary files (downloaded AEM packages, etc) tmp_dir: aem/home/tmp # Location of supportive tools (downloaded Java, OakRun, unpacked AEM SDK) @@ -270,6 +231,45 @@ output: # Controls where outputs and logs should be written to when format is 'text' (console|file|both) mode: console +vendor: + # AEM instance source files + quickstart: + # AEM SDK ZIP or JAR + dist_file: "aem/home/lib/{aem-sdk,cq-quickstart}-*.{zip,jar}" + # AEM License properties file + license_file: "aem/home/lib/license.properties" + + # JDK used to: run AEM instances, build OSGi bundles, assemble AEM packages + java: + # Require following versions before e.g running AEM instances + version_constraints: ">= 11, < 12" + + # Pre-installed local JDK dir + # a) keep it empty to download open source Java automatically for current OS and architecture + # b) set it to absolute path or to env var '[[.Env.JAVA_HOME]]' to indicate where closed source Java like Oracle is installed + home_dir: "" + + # Auto-installed JDK options + download: + # Source URL with template vars support + url: "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.25%2B9/OpenJDK11U-jdk_[[.Arch]]_[[.Os]]_hotspot_11.0.25_9.[[.ArchiveExt]]" + # Map source URL template vars to be compatible with Adoptium Java + replacements: + # Var 'Os' (GOOS) + "darwin": "mac" + # Var 'Arch' (GOARCH) + "x86_64": "x64" + "amd64": "x64" + "386": "x86-32" + # enforce non-ARM Java as some AEM features are not working on ARM (e.g Scene7) + "arm64": "x64" + "aarch64": "x64" + + # Oak Run tool options (offline instance management) + oak_run: + download_url: "https://repo1.maven.org/maven2/org/apache/jackrabbit/oak-run/1.72.0/oak-run-1.72.0.jar" + store_path: "crx-quickstart/repository/segmentstore" + # Content clean options content: clean: diff --git a/pkg/base.go b/pkg/base.go new file mode 100644 index 00000000..d6671f3c --- /dev/null +++ b/pkg/base.go @@ -0,0 +1,40 @@ +package pkg + +import ( + "github.com/wttech/aemc/pkg/common/pathx" +) + +type BaseOpts struct { + aem *AEM + + LibDir string + TmpDir string + ToolDir string + CacheDir string +} + +func NewBaseOpts(aem *AEM) *BaseOpts { + cv := aem.config.Values() + + return &BaseOpts{ + aem: aem, + + LibDir: cv.GetString("base.lib_dir"), + TmpDir: cv.GetString("base.tmp_dir"), + ToolDir: cv.GetString("base.tool_dir"), + CacheDir: cv.GetString("base.cache_dir"), + } +} + +func (o *BaseOpts) PrepareWithChanged() (bool, error) { + changed := false + dirs := []string{o.LibDir, o.TmpDir, o.ToolDir, o.CacheDir} + for _, dir := range dirs { + dirEnsured, err := pathx.EnsureWithChanged(dir) + changed = changed || dirEnsured + if err != nil { + return changed, err + } + } + return changed, nil +} diff --git a/pkg/base/base.go b/pkg/base/base.go deleted file mode 100644 index e9b2b55c..00000000 --- a/pkg/base/base.go +++ /dev/null @@ -1,40 +0,0 @@ -package base - -import ( - "github.com/wttech/aemc/pkg/cfg" - "github.com/wttech/aemc/pkg/common/pathx" -) - -type Opts struct { - config *cfg.Config - - TmpDir string - ToolDir string - CacheDir string -} - -func NewOpts(config *cfg.Config) *Opts { - cv := config.Values() - - return &Opts{ - config: config, - - TmpDir: cv.GetString("base.tmp_dir"), - ToolDir: cv.GetString("base.tool_dir"), - CacheDir: cv.GetString("base.cache_dir"), - } -} - -func (o *Opts) Config() *cfg.Config { - return o.config -} - -func (o *Opts) Prepare() error { - if err := pathx.Ensure(o.TmpDir); err != nil { - return err - } - if err := pathx.Ensure(o.ToolDir); err != nil { - return err - } - return nil -} diff --git a/pkg/cfg/defaults.go b/pkg/cfg/defaults.go index f282e9ad..e41e6be0 100644 --- a/pkg/cfg/defaults.go +++ b/pkg/cfg/defaults.go @@ -17,6 +17,7 @@ func (c *Config) setDefaults() { v.SetDefault("log.timestamp_format", "2006-01-02 15:04:05") v.SetDefault("log.full_timestamp", true) + v.SetDefault("base.lib_dir", common.LibDir) v.SetDefault("base.tmp_dir", common.TmpDir) v.SetDefault("base.tool_dir", common.ToolDir) v.SetDefault("base.cache_dir", common.CacheDir) @@ -31,12 +32,18 @@ func (c *Config) setDefaults() { v.SetDefault("output.log.mode", OutputLogConsole) v.SetDefault("output.query", "") - v.SetDefault("java.home_dir", "") - v.SetDefault("java.version_constraints", ">= 11, < 12") - v.SetDefault("java.download.url", c.tplString("https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.18%2B10/OpenJDK11U-jdk_[[.Arch]]_[[.Os]]_hotspot_11.0.18_10.[[.ArchiveExt]]")) - v.SetDefault("java.download.replacements", map[string]string{"darwin": "mac", "x86_64": "x64", "amd64": "x64", "386": "x86-32", "arm64": "x64", "aarch64": "x64"}) + v.SetDefault("vendor.quickstart.dist_file", common.QuickstartDistFile) + v.SetDefault("vendor.quickstart.license_file", common.QuickstartLicenseFile) - v.SetDefault("vault.download_url", "https://repo1.maven.org/maven2/org/apache/jackrabbit/vault/vault-cli/3.7.2/vault-cli-3.7.2-bin.tar.gz") + v.SetDefault("vendor.java.home_dir", "") + v.SetDefault("vendor.java.version_constraints", ">= 11, < 12") + v.SetDefault("vendor.java.download.url", c.tplString("https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.25%2B9/OpenJDK11U-jdk_[[.Arch]]_[[.Os]]_hotspot_11.0.25_9.[[.ArchiveExt]]")) + v.SetDefault("vendor.java.download.replacements", map[string]string{"darwin": "mac", "x86_64": "x64", "amd64": "x64", "386": "x86-32", "arm64": "x64", "aarch64": "x64"}) + + v.SetDefault("vendor.oak_run.download_url", "https://repo1.maven.org/maven2/org/apache/jackrabbit/oak-run/1.72.0/oak-run-1.72.0.jar") + v.SetDefault("vendor.oak_run.store_path", "crx-quickstart/repository/segmentstore") + + v.SetDefault("vendor.vault.download_url", "https://repo1.maven.org/maven2/org/apache/jackrabbit/vault/vault-cli/3.8.2/vault-cli-3.8.2-bin.tar.gz") v.SetDefault("instance.processing_mode", instance.ProcessingAuto) @@ -86,16 +93,9 @@ func (c *Config) setDefaults() { v.SetDefault("instance.local.tool_dir", common.ToolDir) v.SetDefault("instance.local.unpack_dir", common.VarDir+"/instance") v.SetDefault("instance.local.override_dir", common.DefaultDir+"/"+common.VarDirName+"/instance") - - v.SetDefault("instance.local.quickstart.dist_file", common.LibDir+"/{aem-sdk,cq-quickstart}-*.{zip,jar}") - v.SetDefault("instance.local.quickstart.license_file", common.LibDir+"/license.properties") - v.SetDefault("instance.local.await_strict", true) v.SetDefault("instance.local.service_mode", false) - v.SetDefault("instance.local.oak_run.download_url", "https://repo1.maven.org/maven2/org/apache/jackrabbit/oak-run/1.44.0/oak-run-1.44.0.jar") - v.SetDefault("instance.local.oak_run.store_path", "crx-quickstart/repository/segmentstore") - v.SetDefault("instance.status.timeout", time.Millisecond*500) v.SetDefault("instance.package.upload_optimized", true) diff --git a/pkg/common/constants.go b/pkg/common/constants.go index 978fe33f..d8a3c378 100644 --- a/pkg/common/constants.go +++ b/pkg/common/constants.go @@ -24,6 +24,10 @@ const ( DefaultDirName = "default" DefaultDir = MainDir + "/" + DefaultDirName DispatcherHomeDir = "dispatcher/home" + + QuickstartDistFile = LibDir + "/{aem-sdk,cq-quickstart}-*.{zip,jar}" + QuickstartLicenseFile = LibDir + "/" + QuickstartLicenseFilename + QuickstartLicenseFilename = "license.properties" ) const ( diff --git a/pkg/common/pathx/pathx.go b/pkg/common/pathx/pathx.go index ac2a3543..f4c39aab 100644 --- a/pkg/common/pathx/pathx.go +++ b/pkg/common/pathx/pathx.go @@ -89,6 +89,21 @@ func Ensure(path string) error { return nil } +func EnsureWithChanged(path string) (bool, error) { + exists, err := ExistsStrict(path) + if err != nil { + return false, err + } + if exists { + return false, nil + } + err = Ensure(path) + if err != nil { + return false, err + } + return true, nil +} + func Ext(path string) string { return strings.TrimPrefix(filepath.Ext(path), ".") } diff --git a/pkg/content/content.go b/pkg/content/editor.go similarity index 90% rename from pkg/content/content.go rename to pkg/content/editor.go index ff13f18f..39f05ebb 100644 --- a/pkg/content/content.go +++ b/pkg/content/editor.go @@ -5,7 +5,7 @@ import ( "github.com/samber/lo" log "github.com/sirupsen/logrus" "github.com/spf13/cast" - "github.com/wttech/aemc/pkg/base" + "github.com/wttech/aemc/pkg/cfg" "github.com/wttech/aemc/pkg/common/pathx" "github.com/wttech/aemc/pkg/common/stringsx" "io/fs" @@ -39,9 +39,7 @@ func init() { fileWithNamespacePatternRegex = regexp.MustCompile(FileWithNamespacePattern) } -type Manager struct { - baseOpts *base.Opts - +type Editor struct { FilesDeleted []PathRule FilesFlattened []string PropertiesSkipped []PathRule @@ -49,12 +47,10 @@ type Manager struct { NamespacesSkipped bool } -func NewManager(baseOpts *base.Opts) *Manager { - cv := baseOpts.Config().Values() - - return &Manager{ - baseOpts: baseOpts, +func NewEditor(config *cfg.Config) *Editor { + cv := config.Values() + return &Editor{ FilesDeleted: determinePathRules(cv.Get("content.clean.files_deleted")), FilesFlattened: cv.GetStringSlice("content.clean.files_flattened"), PropertiesSkipped: determinePathRules(cv.Get("content.clean.properties_skipped")), @@ -63,7 +59,7 @@ func NewManager(baseOpts *base.Opts) *Manager { } } -func (c Manager) Clean(path string) error { +func (c Editor) Clean(path string) error { if pathx.IsDir(path) { log.Infof("cleaning directory '%s'", path) if err := c.cleanDotContents(path); err != nil { @@ -107,13 +103,13 @@ func eachFiles(root string, processFileFunc func(string) error) error { }) } -func (c Manager) cleanDotContents(root string) error { +func (c Editor) cleanDotContents(root string) error { return eachFiles(root, func(path string) error { return c.cleanDotContentFile(path) }) } -func (c Manager) cleanDotContentFile(path string) error { +func (c Editor) cleanDotContentFile(path string) error { if !strings.HasSuffix(path, XmlFileSuffix) { return nil } @@ -127,7 +123,7 @@ func (c Manager) cleanDotContentFile(path string) error { return writeLines(path, outputLines) } -func (c Manager) filterLines(path string, lines []string) []string { +func (c Editor) filterLines(path string, lines []string) []string { var result []string for _, line := range lines { flag, processedLine := c.lineProcess(path, line) @@ -148,7 +144,7 @@ func (c Manager) filterLines(path string, lines []string) []string { return c.cleanNamespaces(path, result) } -func (c Manager) cleanNamespaces(path string, lines []string) []string { +func (c Editor) cleanNamespaces(path string, lines []string) []string { if !c.NamespacesSkipped { return lines } @@ -183,7 +179,7 @@ func (c Manager) cleanNamespaces(path string, lines []string) []string { return result } -func (c Manager) lineProcess(path string, line string) (bool, string) { +func (c Editor) lineProcess(path string, line string) (bool, string) { groups := propPatternRegex.FindStringSubmatch(line) if strings.TrimSpace(line) == "" { return true, "" @@ -197,7 +193,7 @@ func (c Manager) lineProcess(path string, line string) (bool, string) { return false, line } -func (c Manager) normalizeMixins(path string, line string, propValue string, lineSuffix string) (bool, string) { +func (c Editor) normalizeMixins(path string, line string, propValue string, lineSuffix string) (bool, string) { normalizedValue := strings.Trim(propValue, "[]") var resultValues []string for _, value := range strings.Split(normalizedValue, ",") { @@ -211,13 +207,13 @@ func (c Manager) normalizeMixins(path string, line string, propValue string, lin return false, strings.ReplaceAll(line, normalizedValue, strings.Join(resultValues, ",")) } -func (c Manager) flattenFiles(root string) error { +func (c Editor) flattenFiles(root string) error { return eachFiles(root, func(path string) error { return c.flattenFile(path) }) } -func (c Manager) flattenFile(path string) error { +func (c Editor) flattenFile(path string) error { if !matchString(path, c.FilesFlattened) { return nil } @@ -231,7 +227,7 @@ func (c Manager) flattenFile(path string) error { return os.Rename(path, dest) } -func (c Manager) deleteFiles(root string) error { +func (c Editor) deleteFiles(root string) error { return eachFiles(root, func(path string) error { return c.DeleteFile(path, func() bool { return matchAnyRule(path, path, c.FilesDeleted) @@ -239,7 +235,7 @@ func (c Manager) deleteFiles(root string) error { }) } -func (c Manager) DeleteDir(path string) error { +func (c Editor) DeleteDir(path string) error { if !pathx.Exists(path) { return nil } @@ -247,7 +243,7 @@ func (c Manager) DeleteDir(path string) error { return os.RemoveAll(path) } -func (c Manager) DeleteFile(path string, allowedFunc func() bool) error { +func (c Editor) DeleteFile(path string, allowedFunc func() bool) error { if !pathx.Exists(path) || allowedFunc != nil && !allowedFunc() { return nil } diff --git a/pkg/content_manager.go b/pkg/content_manager.go index 37c5fd86..2af374a4 100644 --- a/pkg/content_manager.go +++ b/pkg/content_manager.go @@ -15,26 +15,18 @@ const ( ) type ContentManager struct { - aem *AEM - contentManager *content.Manager + aem *AEM + editor *content.Editor } func NewContentManager(aem *AEM) *ContentManager { - return &ContentManager{ - aem: aem, - contentManager: content.NewManager(aem.baseOpts), - } -} - -func (cm *ContentManager) tmpDir() string { - if cm.aem.Detached() { - return os.TempDir() - } - return cm.aem.baseOpts.TmpDir + result := &ContentManager{aem: aem} + result.editor = content.NewEditor(result.aem.config) + return result } func (cm *ContentManager) Clean(path string) error { - return cm.contentManager.Clean(path) + return cm.editor.Clean(path) } func (cm *ContentManager) Download(instance *Instance, localFile string, clean bool, opts PackageCreateOpts) error { @@ -65,7 +57,7 @@ func (cm *ContentManager) PullDir(instance *Instance, dir string, clean bool, re return err } if replace { - if err := cm.contentManager.DeleteDir(dir); err != nil { + if err := cm.editor.DeleteDir(dir); err != nil { return err } } @@ -89,7 +81,7 @@ func (cm *ContentManager) PullFile(instance *Instance, file string, clean bool, } syncFile := DetermineSyncFile(workDir, file) if file != syncFile || replace { - if err := cm.contentManager.DeleteFile(file, nil); err != nil { + if err := cm.editor.DeleteFile(file, nil); err != nil { return err } } @@ -194,3 +186,10 @@ func (cm *ContentManager) pushContent(instances []Instance, pkgFile string) erro } return nil } + +func (cm *ContentManager) tmpDir() string { + if cm.aem.Detached() { + return os.TempDir() + } + return cm.aem.baseOpts.TmpDir +} diff --git a/pkg/facade.go b/pkg/facade.go index 18dd222e..8f482365 100644 --- a/pkg/facade.go +++ b/pkg/facade.go @@ -2,10 +2,7 @@ package pkg import ( - "github.com/wttech/aemc/pkg/base" "github.com/wttech/aemc/pkg/cfg" - "github.com/wttech/aemc/pkg/java" - "github.com/wttech/aemc/pkg/project" "io" "os" "os/exec" @@ -13,13 +10,14 @@ import ( // AEM is a facade to access AEM-related API type AEM struct { - output io.Writer - config *cfg.Config - project *project.Project - baseOpts *base.Opts - javaOpts *java.Opts - contentManager *ContentManager + output io.Writer + config *cfg.Config + project *Project + baseOpts *BaseOpts + + vendorManager *VendorManager instanceManager *InstanceManager + contentManager *ContentManager } func DefaultAEM() *AEM { @@ -30,11 +28,11 @@ func NewAEM(config *cfg.Config) *AEM { result := new(AEM) result.output = os.Stdout result.config = config - result.project = project.New(result.config) - result.baseOpts = base.NewOpts(result.config) - result.javaOpts = java.NewOpts(result.baseOpts) - result.contentManager = NewContentManager(result) + result.project = NewProject(result) + result.baseOpts = NewBaseOpts(result) + result.vendorManager = NewVendorManager(result) result.instanceManager = NewInstanceManager(result) + result.contentManager = NewContentManager(result) return result } @@ -55,26 +53,26 @@ func (a *AEM) Config() *cfg.Config { return a.config } -func (a *AEM) BaseOpts() *base.Opts { +func (a *AEM) BaseOpts() *BaseOpts { return a.baseOpts } -func (a *AEM) JavaOpts() *java.Opts { - return a.javaOpts +func (a *AEM) VendorManager() *VendorManager { + return a.vendorManager } func (a *AEM) InstanceManager() *InstanceManager { return a.instanceManager } -func (a *AEM) Project() *project.Project { - return a.project -} - func (a *AEM) ContentManager() *ContentManager { return a.contentManager } +func (a *AEM) Project() *Project { + return a.project +} + func (a *AEM) Detached() bool { return !a.config.TemplateFileExists() } diff --git a/pkg/java/java.go b/pkg/java_manager.go similarity index 52% rename from pkg/java/java.go rename to pkg/java_manager.go index b2aac6ad..27b2d988 100644 --- a/pkg/java/java.go +++ b/pkg/java_manager.go @@ -1,4 +1,4 @@ -package java +package pkg import ( "fmt" @@ -9,7 +9,6 @@ import ( "github.com/hashicorp/go-version" "github.com/samber/lo" log "github.com/sirupsen/logrus" - "github.com/wttech/aemc/pkg/base" "github.com/wttech/aemc/pkg/common/filex" "github.com/wttech/aemc/pkg/common/httpx" "github.com/wttech/aemc/pkg/common/osx" @@ -17,8 +16,8 @@ import ( "github.com/wttech/aemc/pkg/common/stringsx" ) -type Opts struct { - baseOpts *base.Opts +type JavaManager struct { + vendorManager *VendorManager HomeDir string DownloadURL string @@ -26,16 +25,16 @@ type Opts struct { VersionConstraints string } -func NewOpts(baseOpts *base.Opts) *Opts { - cv := baseOpts.Config().Values() +func NewJavaManager(manager *VendorManager) *JavaManager { + cv := manager.aem.Config().Values() - return &Opts{ - baseOpts: baseOpts, + return &JavaManager{ + vendorManager: manager, - HomeDir: cv.GetString("java.home_dir"), - DownloadURL: cv.GetString("java.download.url"), - DownloadURLReplacements: cv.GetStringMapString("java.download.replacements"), - VersionConstraints: cv.GetString("java.version_constraints"), + HomeDir: cv.GetString("vendor.java.home_dir"), + DownloadURL: cv.GetString("vendor.java.download.url"), + DownloadURLReplacements: cv.GetStringMapString("vendor.java.download.replacements"), + VersionConstraints: cv.GetString("vendor.java.version_constraints"), } } @@ -43,108 +42,111 @@ type DownloadLock struct { Source string `yaml:"source"` } -func (o *Opts) toolDir() string { - return fmt.Sprintf("%s/%s", o.baseOpts.ToolDir, "java") +func (jm *JavaManager) toolDir() string { + return fmt.Sprintf("%s/%s", jm.vendorManager.aem.baseOpts.ToolDir, "java") } -func (o *Opts) archiveDir() string { - return fmt.Sprintf("%s/%s", o.toolDir(), "archive") +func (jm *JavaManager) archiveDir() string { + return fmt.Sprintf("%s/%s", jm.toolDir(), "archive") } -func (o *Opts) downloadLock() osx.Lock[DownloadLock] { - return osx.NewLock(fmt.Sprintf("%s/lock/create.yml", o.toolDir()), func() (DownloadLock, error) { return DownloadLock{Source: o.DownloadURL}, nil }) +func (jm *JavaManager) downloadLock() osx.Lock[DownloadLock] { + return osx.NewLock(fmt.Sprintf("%s/lock/create.yml", jm.toolDir()), func() (DownloadLock, error) { return DownloadLock{Source: jm.DownloadURL}, nil }) } -func (o *Opts) jdkDir() string { - return fmt.Sprintf("%s/%s", o.toolDir(), "jdk") +func (jm *JavaManager) jdkDir() string { + return fmt.Sprintf("%s/%s", jm.toolDir(), "jdk") } -func (o *Opts) Prepare() error { - if o.HomeDir == "" && o.DownloadURL != "" { - if err := o.download(); err != nil { - return err +func (jm *JavaManager) PrepareWithChanged() (bool, error) { + changed := false + if jm.HomeDir == "" && jm.DownloadURL != "" { + downloaded, err := jm.download() + changed = downloaded + if err != nil { + return downloaded, err } } - if err := o.checkVersion(); err != nil { - return err + if err := jm.checkVersion(); err != nil { + return changed, err } - return nil + return changed, nil } -func (o *Opts) download() error { - lock := o.downloadLock() +func (jm *JavaManager) download() (bool, error) { + lock := jm.downloadLock() check, err := lock.State() if err != nil { - return err + return false, err } if check.UpToDate { log.Debugf("existing JDK '%s' is up-to-date", check.Locked.Source) - return nil + return false, nil } - log.Infof("preparing new JDK at dir '%s'", o.jdkDir()) - if err = o.prepare(err); err != nil { - return err + log.Infof("preparing new JDK at dir '%s'", jm.jdkDir()) + if err = jm.prepare(err); err != nil { + return false, err } if err := lock.Lock(); err != nil { - return err + return false, err } - log.Infof("prepared new JDK at dir '%s'", o.jdkDir()) - return nil + log.Infof("prepared new JDK at dir '%s'", jm.jdkDir()) + return true, nil } -func (o *Opts) prepare(err error) error { - if err := pathx.DeleteIfExists(o.jdkDir()); err != nil { +func (jm *JavaManager) prepare(err error) error { + if err := pathx.DeleteIfExists(jm.jdkDir()); err != nil { return err } - url := o.DownloadURL - for search, replace := range o.DownloadURLReplacements { + url := jm.DownloadURL + for search, replace := range jm.DownloadURLReplacements { url = strings.ReplaceAll(url, search, replace) } - archiveFile := fmt.Sprintf("%s/%s", o.archiveDir(), httpx.FileNameFromURL(url)) + archiveFile := fmt.Sprintf("%s/%s", jm.archiveDir(), httpx.FileNameFromURL(url)) log.Infof("downloading new JDK from URL '%s' to file '%s'", url, archiveFile) if err := httpx.DownloadOnce(url, archiveFile); err != nil { return err } log.Infof("downloaded new JDK from URL '%s' to file '%s'", url, archiveFile) - if _, err = filex.UnarchiveWithChanged(archiveFile, o.jdkDir()); err != nil { + if _, err = filex.UnarchiveWithChanged(archiveFile, jm.jdkDir()); err != nil { return err } return nil } -func (o *Opts) checkVersion() error { - currentVersion, err := o.CurrentVersion() +func (jm *JavaManager) checkVersion() error { + currentVersion, err := jm.CurrentVersion() if err != nil { return err } - if o.VersionConstraints != "" { - versionConstraints, err := version.NewConstraint(o.VersionConstraints) + if jm.VersionConstraints != "" { + versionConstraints, err := version.NewConstraint(jm.VersionConstraints) if err != nil { - return fmt.Errorf("java version constraint '%s' is invalid: %w", o.VersionConstraints, err) + return fmt.Errorf("java version constraint '%s' is invalid: %w", jm.VersionConstraints, err) } if !versionConstraints.Check(currentVersion) { - return fmt.Errorf("java current version '%s' does not meet contraints '%s'", currentVersion, o.VersionConstraints) + return fmt.Errorf("java current version '%s' does not meet contraints '%s'", currentVersion, jm.VersionConstraints) } } return nil } -func (o *Opts) FindHomeDir() (string, error) { +func (jm *JavaManager) FindHomeDir() (string, error) { var homeDir string - if o.HomeDir == "" { - files, err := os.ReadDir(o.jdkDir()) + if jm.HomeDir == "" { + files, err := os.ReadDir(jm.jdkDir()) if err != nil { return "", err } var dir string for _, file := range files { if file.IsDir() && strings.HasPrefix(file.Name(), "jdk") { - dir = fmt.Sprintf("%s/%s", o.jdkDir(), file.Name()) + dir = fmt.Sprintf("%s/%s", jm.jdkDir(), file.Name()) break } } if dir == "" { - return "", fmt.Errorf("java home dir cannot be found in unarchived JDK contents under path '%s'", o.archiveDir()) + return "", fmt.Errorf("java home dir cannot be found in unarchived JDK contents under path '%s'", jm.archiveDir()) } if err != nil { return "", err @@ -155,7 +157,7 @@ func (o *Opts) FindHomeDir() (string, error) { homeDir = dir } } else { - homeDir = o.HomeDir + homeDir = jm.HomeDir } homeDir = pathx.Canonical(homeDir) if !pathx.Exists(homeDir) { @@ -164,8 +166,8 @@ func (o *Opts) FindHomeDir() (string, error) { return homeDir, nil } -func (o *Opts) Executable() (string, error) { - homeDir, err := o.FindHomeDir() +func (jm *JavaManager) Executable() (string, error) { + homeDir, err := jm.FindHomeDir() if err != nil { return "", err } @@ -175,8 +177,8 @@ func (o *Opts) Executable() (string, error) { return pathx.Canonical(homeDir + "/bin/java"), nil } -func (o *Opts) Env() ([]string, error) { - homeDir, err := o.FindHomeDir() +func (jm *JavaManager) Env() ([]string, error) { + homeDir, err := jm.FindHomeDir() if err != nil { return nil, err } @@ -190,13 +192,13 @@ func (o *Opts) Env() ([]string, error) { return envFinal, nil } -func (o *Opts) Command(args ...string) (*exec.Cmd, error) { - executable, err := o.Executable() +func (jm *JavaManager) Command(args ...string) (*exec.Cmd, error) { + executable, err := jm.Executable() if err != nil { return nil, err } cmd := exec.Command(executable, args...) - env, err := o.Env() + env, err := jm.Env() if err != nil { return nil, err } @@ -204,8 +206,8 @@ func (o *Opts) Command(args ...string) (*exec.Cmd, error) { return cmd, nil } -func (o *Opts) CurrentVersion() (*version.Version, error) { - currentText, err := o.readCurrentVersion() +func (jm *JavaManager) CurrentVersion() (*version.Version, error) { + currentText, err := jm.readCurrentVersion() if err != nil { return nil, err } @@ -216,8 +218,8 @@ func (o *Opts) CurrentVersion() (*version.Version, error) { return current, nil } -func (o *Opts) readCurrentVersion() (string, error) { - cmd, err := o.Command("-version") +func (jm *JavaManager) readCurrentVersion() (string, error) { + cmd, err := jm.Command("-version") if err != nil { return "", err } diff --git a/pkg/local_instance.go b/pkg/local_instance.go index 461d7314..27029def 100644 --- a/pkg/local_instance.go +++ b/pkg/local_instance.go @@ -12,7 +12,6 @@ import ( "github.com/wttech/aemc/pkg/common/stringsx" "github.com/wttech/aemc/pkg/common/timex" "github.com/wttech/aemc/pkg/instance" - "github.com/wttech/aemc/pkg/java" "os" "os/exec" "path/filepath" @@ -103,8 +102,12 @@ func (li LocalInstance) LocalOpts() *LocalOpts { return li.instance.manager.LocalOpts } -func (li LocalInstance) JavaOpts() *java.Opts { - return li.instance.manager.aem.javaOpts +func (li LocalInstance) JavaManager() *JavaManager { + return li.instance.manager.aem.VendorManager().JavaManager() +} + +func (li LocalInstance) VendorManager() *VendorManager { + return li.instance.manager.aem.VendorManager() } func (li LocalInstance) Name() string { @@ -175,7 +178,7 @@ func (li LocalInstance) binCbpExecutable() string { } func (li LocalInstance) LicenseFile() string { - return pathx.Canonical(li.Dir() + "/" + LicenseFilename) + return pathx.Canonical(li.Dir() + "/" + common.QuickstartLicenseFilename) } var ( @@ -257,7 +260,7 @@ func (li LocalInstance) Import() error { func (li LocalInstance) createLock() osx.Lock[localInstanceCreateLock] { return osx.NewLock(fmt.Sprintf("%s/create.yml", li.StateDir()), func() (localInstanceCreateLock, error) { var zero localInstanceCreateLock - jar, err := li.LocalOpts().Jar() + jar, err := li.VendorManager().InstanceJar() if err != nil { return zero, err } @@ -271,11 +274,11 @@ type localInstanceCreateLock struct { func (li LocalInstance) unpackJarFile() error { log.Infof("%s > unpacking files", li.instance.IDColor()) - jar, err := li.LocalOpts().Jar() + jar, err := li.VendorManager().InstanceJar() if err != nil { return err } - cmd, err := li.JavaOpts().Command( + cmd, err := li.JavaManager().Command( "-Djava.awt.headless=true", "-jar", pathx.Canonical(jar), "-unpack", ) @@ -295,14 +298,14 @@ func (li LocalInstance) unpackJarFile() error { } func (li LocalInstance) copyLicenseFile() error { - sdk, err := li.LocalOpts().Quickstart.IsDistSDK() + sdk, err := li.VendorManager().Quickstart().IsDistSDK() if err != nil { return err } if sdk { return nil } - source := pathx.Canonical(li.LocalOpts().Quickstart.LicenseFile) + source := pathx.Canonical(li.VendorManager().Quickstart().LicenseFile) dest := pathx.Canonical(li.LicenseFile()) log.Infof("%s > copying license file from '%s' to '%s'", li.instance.IDColor(), source, dest) if err := filex.Copy(source, dest, true); err != nil { @@ -456,7 +459,7 @@ func (li LocalInstance) update() error { } func (li LocalInstance) setPassword() error { - return li.LocalOpts().OakRun.SetPassword(li.Dir(), LocalInstanceUser, li.instance.password) + return li.VendorManager().OakRun().SetPassword(li.Dir(), LocalInstanceUser, li.instance.password) } func (li LocalInstance) copyOverrideDirs() error { @@ -813,7 +816,7 @@ func (li LocalInstance) binScriptCommand(name string, verbose bool) (*exec.Cmd, } cmd := execx.CommandShell(args) cmd.Dir = li.Dir() - env, err := li.JavaOpts().Env() + env, err := li.JavaManager().Env() if err != nil { return nil, err } diff --git a/pkg/local_instance_manager.go b/pkg/local_instance_manager.go index 0f120675..01d39ac8 100644 --- a/pkg/local_instance_manager.go +++ b/pkg/local_instance_manager.go @@ -6,7 +6,6 @@ import ( "github.com/dustin/go-humanize" "github.com/samber/lo" log "github.com/sirupsen/logrus" - "github.com/wttech/aemc/pkg/common" "github.com/wttech/aemc/pkg/common/fmtx" "github.com/wttech/aemc/pkg/common/pathx" "github.com/wttech/aemc/pkg/common/timex" @@ -15,16 +14,6 @@ import ( "time" ) -const ( - UnpackDir = common.VarDir + "/instance" - BackupDir = common.VarDir + "/backup" - OverrideDir = common.DefaultDir + "/" + common.VarDirName + "/instance" - - DistFile = common.LibDir + "/{aem-sdk,cq-quickstart}-*.{zip,jar}" - LicenseFile = common.LibDir + "/" + LicenseFilename - LicenseFilename = "license.properties" -) - type LocalOpts struct { manager *InstanceManager @@ -32,9 +21,6 @@ type LocalOpts struct { BackupDir string OverrideDir string ServiceMode bool - OakRun *OakRun - Quickstart *Quickstart - SDK *SDK } func NewLocalOpts(manager *InstanceManager) *LocalOpts { @@ -45,24 +31,21 @@ func NewLocalOpts(manager *InstanceManager) *LocalOpts { result.BackupDir = cfg.GetString("instance.local.backup_dir") result.OverrideDir = cfg.GetString("instance.local.override_dir") result.ServiceMode = cfg.GetBool("instance.local.service_mode") - result.Quickstart = NewQuickstart(result) - result.SDK = NewSDK(result) - result.OakRun = NewOakRun(result) return result } -func (o *LocalOpts) Initialize() error { +func (o *LocalOpts) Validate() error { // validate phase (fast feedback) if err := o.validateUnpackDir(); err != nil { return err } - sdk, err := o.Quickstart.IsDistSDK() + sdk, err := o.manager.aem.vendorManager.Quickstart().IsDistSDK() if err != nil { return err } if !sdk { - _, err = o.Quickstart.FindLicenseFile() + _, err = o.manager.aem.vendorManager.Quickstart().FindLicenseFile() if err != nil { return err } @@ -73,22 +56,8 @@ func (o *LocalOpts) Initialize() error { } } // preparation phase - if err := o.manager.aem.baseOpts.Prepare(); err != nil { - return err - } - if err := o.manager.aem.javaOpts.Prepare(); err != nil { - return err - } - if sdk { - if err := o.SDK.Prepare(); err != nil { - return err - } - } - if err := o.OakRun.Prepare(); err != nil { - return err - } for _, instance := range o.manager.Locals() { - if err := instance.Local().CheckRecreationNeeded(); err != nil { // depends on SDK prepare + if err := instance.Local().CheckRecreationNeeded(); err != nil { // depends on sdk prepare return err } } @@ -111,23 +80,12 @@ func (o *LocalOpts) validateUnpackDir() error { return nil } -func (o *LocalOpts) Jar() (string, error) { - sdk, err := o.Quickstart.IsDistSDK() - if err != nil { - return "", err - } - if sdk { - return o.SDK.QuickstartJar() - } - return o.Quickstart.FindDistFile() -} - -func NewQuickstart(localOpts *LocalOpts) *Quickstart { - cfg := localOpts.manager.aem.config.Values() +func NewQuickstart(manager *VendorManager) *Quickstart { + cfg := manager.aem.config.Values() return &Quickstart{ - DistFile: cfg.GetString("instance.local.quickstart.dist_file"), - LicenseFile: cfg.GetString("instance.local.quickstart.license_file"), + DistFile: cfg.GetString("vendor.quickstart.dist_file"), + LicenseFile: cfg.GetString("vendor.quickstart.license_file"), } } @@ -158,7 +116,7 @@ func (im *InstanceManager) CreateAll() ([]Instance, error) { func (im *InstanceManager) Create(instances []Instance) ([]Instance, error) { created := []Instance{} - if err := im.LocalOpts.Initialize(); err != nil { + if err := im.LocalOpts.Validate(); err != nil { return created, err } log.Info(InstancesMsg(instances, "creating")) @@ -233,7 +191,7 @@ func (im *InstanceManager) Start(instances []Instance) ([]Instance, error) { log.Debugf("no instances to start") return []Instance{}, nil } - if err := im.LocalOpts.Initialize(); err != nil { + if err := im.LocalOpts.Validate(); err != nil { return []Instance{}, err } diff --git a/pkg/oak_run.go b/pkg/oak_run.go index 865bab9d..1ca19343 100644 --- a/pkg/oak_run.go +++ b/pkg/oak_run.go @@ -11,19 +11,19 @@ import ( "path/filepath" ) -func NewOakRun(localOpts *LocalOpts) *OakRun { - cv := localOpts.manager.aem.Config().Values() +func NewOakRun(vendorManager *VendorManager) *OakRun { + cv := vendorManager.aem.Config().Values() return &OakRun{ - localOpts: localOpts, + vendorManager: vendorManager, - DownloadURL: cv.GetString("instance.local.oak_run.download_url"), - StorePath: cv.GetString("instance.local.oak_run.store_path"), + DownloadURL: cv.GetString("vendor.oak_run.download_url"), + StorePath: cv.GetString("vendor.oak_run.store_path"), } } type OakRun struct { - localOpts *LocalOpts + vendorManager *VendorManager DownloadURL string StorePath string @@ -34,35 +34,35 @@ type OakRunLock struct { } func (or OakRun) Dir() string { - return or.localOpts.manager.aem.baseOpts.ToolDir + "/oak-run" + return or.vendorManager.aem.baseOpts.ToolDir + "/oak-run" } func (or OakRun) lock() osx.Lock[OakRunLock] { return osx.NewLock(or.Dir()+"/lock/create.yml", func() (OakRunLock, error) { return OakRunLock{DownloadURL: or.DownloadURL}, nil }) } -func (or OakRun) Prepare() error { +func (or OakRun) PrepareWithChanged() (bool, error) { lock := or.lock() check, err := lock.State() if err != nil { - return err + return false, err } if check.UpToDate { log.Debugf("existing OakRun '%s' is up-to-date", or.DownloadURL) - return nil + return false, nil } log.Infof("preparing new OakRun '%s'", or.DownloadURL) err = or.prepare() if err != nil { - return err + return false, err } err = lock.Lock() if err != nil { - return err + return false, err } log.Infof("prepared new OakRun '%s'", or.DownloadURL) - return nil + return true, nil } func (or OakRun) JarFile() string { @@ -99,8 +99,8 @@ func (or OakRun) SetPassword(instanceDir string, user string, password string) e func (or OakRun) RunScript(instanceDir string, scriptFile string) error { storeDir := fmt.Sprintf("%s/%s", instanceDir, or.StorePath) - cmd, err := or.localOpts.manager.aem.javaOpts.Command( - "-Djava.io.tmpdir="+pathx.Canonical(or.localOpts.manager.aem.baseOpts.TmpDir), + cmd, err := or.vendorManager.javaManager.Command( + "-Djava.io.tmpdir="+pathx.Canonical(or.vendorManager.aem.baseOpts.TmpDir), "-jar", or.JarFile(), "console", storeDir, "--read-write", fmt.Sprintf(":load %s", scriptFile), ) diff --git a/pkg/project.go b/pkg/project.go new file mode 100644 index 00000000..8bc5ab65 --- /dev/null +++ b/pkg/project.go @@ -0,0 +1,256 @@ +package pkg + +import ( + "embed" + "fmt" + "github.com/magiconair/properties" + log "github.com/sirupsen/logrus" + "github.com/wttech/aemc/pkg/cfg" + "github.com/wttech/aemc/pkg/common" + "github.com/wttech/aemc/pkg/common/filex" + "github.com/wttech/aemc/pkg/common/osx" + "github.com/wttech/aemc/pkg/common/pathx" + "github.com/wttech/aemc/pkg/project" + "io/fs" + "strings" +) + +type Project struct { + aem *AEM +} + +func NewProject(aem *AEM) *Project { + return &Project{aem} +} + +func (p Project) IsAppKind(kind project.Kind) bool { + return kind == project.KindAppClassic || kind == project.KindAppCloud +} + +func (p Project) IsScaffolded() bool { + return p.aem.config.TemplateFileExists() +} + +func (p Project) ScaffoldWithChanged(kind project.Kind) (bool, error) { + if p.IsScaffolded() { + return false, nil + } + if err := p.Scaffold(kind); err != nil { + return false, err + } + return true, nil +} + +func (p Project) Scaffold(kind project.Kind) error { + if err := p.scaffoldDefaultFiles(kind); err != nil { + return err + } + if err := p.scaffoldGitIgnore(kind); err != nil { + return err + } + if err := p.scaffoldLocalEnvFile(kind); err != nil { + return err + } + return nil +} + +func (p Project) scaffoldDefaultFiles(kind project.Kind) error { + log.Infof("preparing default files for project of kind '%s'", kind) + switch kind { + case project.KindInstance: + if err := copyEmbedFiles(&project.CommonFiles, "common/"); err != nil { + return err + } + if err := copyEmbedFiles(&project.InstanceFiles, "instance/"); err != nil { + return err + } + case project.KindAppClassic: + if err := copyEmbedFiles(&project.CommonFiles, "common/"); err != nil { + return err + } + if err := copyEmbedFiles(&project.AppClassicFiles, "app_classic/"); err != nil { + return err + } + case project.KindAppCloud: + if err := copyEmbedFiles(&project.CommonFiles, "common/"); err != nil { + return err + } + if err := copyEmbedFiles(&project.AppCloudFiles, "app_cloud/"); err != nil { + return err + } + default: + return fmt.Errorf("project kind '%s' cannot be initialized", kind) + } + return nil +} + +func copyEmbedFiles(efs *embed.FS, dirPrefix string) error { + return fs.WalkDir(efs, ".", func(path string, d fs.DirEntry, err error) error { + if d.IsDir() { + return nil + } + bytes, err := efs.ReadFile(path) + if err != nil { + return err + } + if err := filex.Write(strings.TrimPrefix(strings.ReplaceAll(path, "$", ""), dirPrefix), bytes); err != nil { + return err + } + return nil + }) +} + +// need to be in sync with osx.EnvVarsLoad() +func (p Project) scaffoldGitIgnore(kind project.Kind) error { + switch kind { + case project.KindAppClassic, project.KindAppCloud: + return filex.AppendString(project.GitIgnoreFile, osx.LineSep()+strings.Join([]string{ + "", + "# " + common.AppName, + common.HomeDir + "/", + common.DispatcherHomeDir + "/", + ".task/", + "." + osx.EnvFileExt, + "." + osx.EnvFileExt + ".*", + "", + }, osx.LineSep())) + default: + return filex.AppendString(project.GitIgnoreFile, osx.LineSep()+strings.Join([]string{ + "", + "# " + common.AppName, + common.HomeDir + "/", + ".task/", + "." + osx.EnvFileExt, + "." + osx.EnvFileExt + ".*", + "", + }, osx.LineSep())) + } +} + +func (p Project) scaffoldLocalEnvFile(kind project.Kind) error { + if p.IsAppKind(kind) && p.HasProps() { + prop, err := p.Prop(project.PackagePropName) + if err != nil { + return err + } + propTrimmed := strings.TrimSpace(prop) + if propTrimmed != "" { + if err := filex.AppendString(osx.EnvLocalFile, osx.LineSep()+strings.Join([]string{ + "", + "# AEM Application", + "", + "AEM_PACKAGE=" + prop, + "", + }, osx.LineSep())); err != nil { + return err + } + } + } + return nil +} + +func (p Project) KindDetermine(name string) (project.Kind, error) { + var kind project.Kind = project.KindAuto + if name != "" { + kindCandidate, err := project.KindOf(name) + if err != nil { + return "", err + } + kind = kindCandidate + } + if kind == project.KindAuto { + kindCandidate, err := p.KindInfer() + if err != nil { + return "", err + } + kind = kindCandidate + } + return kind, nil +} + +func (p Project) HasProps() bool { + return pathx.Exists(project.PropFile) +} + +func (p Project) Prop(name string) (string, error) { + propLoader := properties.Loader{ + Encoding: properties.ISO_8859_1, + DisableExpansion: true, + } + props, err := propLoader.LoadFile(project.PropFile) + if err != nil { + return "", fmt.Errorf("cannot read project property '%s' from file '%s': %w", name, project.PropFile, err) + } + propValue := props.GetString(name, "") + return propValue, nil +} + +func (p Project) KindInfer() (project.Kind, error) { + if p.HasProps() { + log.Infof("inferring project kind basing on file '%s' and property '%s'", project.PropFile, project.KindPropName) + propValue, err := p.Prop(project.KindPropName) + if err != nil { + return "", err + } + var kind project.Kind + if propValue == project.KindPropCloudValue { + kind = project.KindAppCloud + } else if strings.HasPrefix(propValue, project.KindPropClassicPrefix) { + kind = project.KindAppClassic + } else { + return "", fmt.Errorf("cannot infer project kind as value '%s' of property '%s' in file '%s' is not recognized", propValue, project.KindPropName, project.PropFile) + } + log.Infof("inferred project kind basing on file '%s' and property '%s' is '%s'", project.PropFile, project.KindPropName, kind) + return kind, nil + } + return project.KindUnknown, nil +} + +func (p Project) DirsIgnored() []string { + return []string{common.HomeDir, common.DispatcherHomeDir} +} + +func (p Project) ScaffoldGettingStarted() string { + libDir := p.aem.BaseOpts().LibDir + + text := fmt.Sprintf(strings.Join([]string{ + "As a next step provide AEM files (JAR or SDK ZIP, license, service packs) to directory '" + libDir + "'.", + "Alternatively, instruct the tool where these files are located by adjusting properties: 'dist_file', 'license_file' in configuration file '" + cfg.FileDefault + "'.", + "", + "Use tasks to manage AEM instances and more:", + "", + "sh taskw --list", + "", + "It is also possible to run individual AEM Compose CLI commands separately.", + "Discover available commands by running:", + "", + "sh aemw --help", + }, "\n")) + return text +} + +func (p Project) InitGettingStartedError() string { + libDir := p.aem.BaseOpts().LibDir + text := fmt.Sprintf(strings.Join([]string{ + "Be sure to provide AEM files (JAR or SDK ZIP, license, service packs) to directory '" + libDir + "'.", + }, "\n")) + return text +} + +func (p Project) InitGettingStartedSuccess() string { + text := fmt.Sprintf(strings.Join([]string{ + "AEM Compose project is ready to use.", + "", + fmt.Sprintf("Make sure to exclude the directories from VCS versioning and IDE indexing: %s", strings.Join(p.DirsIgnored(), ", ")), + "", + "Use tasks to manage AEM instances and more:", + "", + "sh taskw --list", + "", + "It is also possible to run individual AEM Compose CLI commands separately.", + "Discover available commands by running:", + "", + "sh aemw --help", + }, "\n")) + return text +} diff --git a/pkg/project/app_classic/Taskfile.yml b/pkg/project/app_classic/Taskfile.yml index 94343a98..e000cbe0 100644 --- a/pkg/project/app_classic/Taskfile.yml +++ b/pkg/project/app_classic/Taskfile.yml @@ -8,6 +8,8 @@ env: AEM_ENV: '{{.AEM_ENV | default "local"}}' AEM_INSTANCE_PROCESSING_MODE: auto AEM_OUTPUT_VALUE: NONE + JAVA_HOME: + sh: sh aemw vendor list --output-value javaHome dotenv: - '.env' # VCS-ignored, user-specific @@ -18,8 +20,7 @@ tasks: init: desc: initialize project cmds: - - sh aemw init - - sh aemw instance init + - sh aemw project init status: desc: check status of AEM instances and dispatcher @@ -192,9 +193,13 @@ tasks: - all/target/*.all-*.zip vars: ARGS: -PfedDev {{.AEM_BUILD_ARGS}} - JAVA_HOME: - sh: sh aemw instance init --output-value javaHome - cmd: JAVA_HOME={{.JAVA_HOME}} mvn clean package {{.ARGS}} + cmd: sh mvnw clean package {{.ARGS}} + + aem:build:version: + desc: check build tool versions + cmds: + - echo "JAVA_HOME=${JAVA_HOME}" + - sh mvnw -version aem:deploy: desc: deploy AEM application @@ -237,12 +242,12 @@ tasks: # dereference symbolic links causing problems on Windows - rm -fr home/src && mkdir -p home && cp -rL src home/src # enforce typical architecture to avoid problems on M1/ARM Mac - - docker build --platform linux/amd64 -t acme/aem-ams/dispatcher-publish . + - '{{.DOCKER_COMMAND}} build --platform linux/amd64 -t acme/aem-ams/dispatcher-publish .' dispatcher:test: desc: test AEM dispatcher image dir: dispatcher - cmd: docker run --rm -it --entrypoint bash acme/aem-ams/dispatcher-publish:latest + cmd: '{{.DOCKER_COMMAND}} run --rm -it --entrypoint bash acme/aem-ams/dispatcher-publish:latest' dispatcher:start: desc: start AEM dispatcher using custom image @@ -251,13 +256,13 @@ tasks: dir: dispatcher cmds: - mkdir -p home/docker/httpd/logs home/docker/httpd/cache/author home/docker/httpd/cache/publish - - docker compose up -d + - '{{.DOCKER_COMPOSE_COMMAND}} up -d' dispatcher:stop: desc: stop AEM dispatcher aliases: [ dispatcher:down ] dir: dispatcher - cmd: docker compose down + cmd: '{{.DOCKER_COMPOSE_COMMAND}} down' dispatcher:restart: desc: restart AEM dispatcher @@ -269,19 +274,19 @@ tasks: desc: check status of AEM dispatcher dir: dispatcher cmds: - - docker compose ps - - docker compose logs --tail 20 + - '{{.DOCKER_COMPOSE_COMMAND}} ps' + - '{{.DOCKER_COMPOSE_COMMAND}} logs --tail 20' dispatcher:login: desc: login to AEM dispatcher shell - cmd: docker exec -it dispatcher bash + cmd: '{{.DOCKER_COMMAND}} exec -it dispatcher bash' ignore_error: true dispatcher:destroy: desc: destroy AEM dispatcher dir: dispatcher cmds: - - docker compose rm -fsv + - '{{.DOCKER_COMPOSE_COMMAND}} down -v --remove-orphans' - rm -fr target/docker target/src dispatcher:hosts: diff --git a/pkg/project/app_classic/aem/default/etc/aem.yml b/pkg/project/app_classic/aem/default/etc/aem.yml index b487e450..2b8e739f 100755 --- a/pkg/project/app_classic/aem/default/etc/aem.yml +++ b/pkg/project/app_classic/aem/default/etc/aem.yml @@ -128,18 +128,6 @@ instance: # Archived runtime dir (AEM backup files '*.aemb.zst') backup_dir: "aem/home/var/backup" - # Oak Run tool options (offline instance management) - oak_run: - download_url: "https://repo1.maven.org/maven2/org/apache/jackrabbit/oak-run/1.44.0/oak-run-1.44.0.jar" - store_path: "crx-quickstart/repository/segmentstore" - - # Source files - quickstart: - # AEM SDK ZIP or JAR - dist_file: 'aem/home/lib/{aem-sdk,cq-quickstart}-*.{zip,jar}' - # AEM License properties file - license_file: "aem/home/lib/license.properties" - # Status discovery (timezone, AEM version, etc) status: timeout: 500ms @@ -220,35 +208,9 @@ instance: timeout: 10m delay: 10s -java: - # Require following versions before e.g running AEM instances - version_constraints: ">= 11, < 12" - - # Pre-installed local JDK dir - # a) keep it empty to download open source Java automatically for current OS and architecture - # b) set it to absolute path or to env var '[[.Env.JAVA_HOME]]' to indicate where closed source Java like Oracle is installed - home_dir: "" - - # Auto-installed JDK options - download: - # Source URL with template vars support - url: "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.18%2B10/OpenJDK11U-jdk_[[.Arch]]_[[.Os]]_hotspot_11.0.18_10.[[.ArchiveExt]]" - # Map source URL template vars to be compatible with Adoptium Java - replacements: - # Var 'Os' (GOOS) - "darwin": "mac" - # Var 'Arch' (GOARCH) - "x86_64": "x64" - "amd64": "x64" - "386": "x86-32" - # enforce non-ARM Java as some AEM features are not working on ARM (e.g Scene7) - "arm64": "x64" - "aarch64": "x64" - -vault: - "download_url": "https://repo1.maven.org/maven2/org/apache/jackrabbit/vault/vault-cli/3.7.2/vault-cli-3.7.2-bin.tar.gz" - base: + # Location of library files (AEM SDK ZIP, Quickstart JAR & License, Crypto keys, service packs, additional packages, etc.) + lib_dir: aem/home/lib # Location of temporary files (downloaded AEM packages, etc) tmp_dir: aem/home/tmp # Location of supportive tools (downloaded Java, OakRun, unpacked AEM SDK) @@ -271,7 +233,46 @@ output: # Controls where outputs and logs should be written to when format is 'text' (console|file|both) mode: console -# Content clean options +vendor: + # AEM instance source files + quickstart: + # AEM SDK ZIP or JAR + dist_file: 'aem/home/lib/{aem-sdk,cq-quickstart}-*.{zip,jar}' + # AEM License properties file + license_file: "aem/home/lib/license.properties" + + # JDK used to: run AEM instances, build OSGi bundles, assemble AEM packages + java: + # Require following versions before e.g running AEM instances + version_constraints: ">= 11, < 12" + + # Pre-installed local JDK dir + # a) keep it empty to download open source Java automatically for current OS and architecture + # b) set it to absolute path or to env var '[[.Env.JAVA_HOME]]' to indicate where closed source Java like Oracle is installed + home_dir: "" + + # Auto-installed JDK options + download: + # Source URL with template vars support + url: "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.25%2B9/OpenJDK11U-jdk_[[.Arch]]_[[.Os]]_hotspot_11.0.25_9.[[.ArchiveExt]]" + # Map source URL template vars to be compatible with Adoptium Java + replacements: + # Var 'Os' (GOOS) + "darwin": "mac" + # Var 'Arch' (GOARCH) + "x86_64": "x64" + "amd64": "x64" + "386": "x86-32" + # enforce non-ARM Java as some AEM features are not working on ARM (e.g Scene7) + "arm64": "x64" + "aarch64": "x64" + + # Oak Run tool options (offline instance management) + oak_run: + download_url: "https://repo1.maven.org/maven2/org/apache/jackrabbit/oak-run/1.72.0/oak-run-1.72.0.jar" + store_path: "crx-quickstart/repository/segmentstore" + +# Content-related options content: clean: # File patterns to be deleted diff --git a/pkg/project/app_classic/dispatcher/compose.yaml b/pkg/project/app_classic/dispatcher/compose.yaml new file mode 100644 index 00000000..9db5c7b7 --- /dev/null +++ b/pkg/project/app_classic/dispatcher/compose.yaml @@ -0,0 +1,14 @@ +services: + dispatcher: + container_name: dispatcher + image: acme/aem-ams/dispatcher-publish:latest + platform: linux/amd64 + ports: + - "80:80" + volumes: + - ./home/docker/httpd/logs:/etc/httpd/logs + - ./home/docker/httpd/cache:/var/www/cache + sysctls: + # Fixes: "Permission denied: AH00072: make_sock: could not bind to address [::]:80" + # See: https://documentation.suse.com/smart/container/html/rootless-podman/index.html#rootless-podman-configure-port-below-1024 + net.ipv4.ip_unprivileged_port_start: 0 diff --git a/pkg/project/app_classic/dispatcher/docker-compose.yml b/pkg/project/app_classic/dispatcher/docker-compose.yml deleted file mode 100644 index c8615543..00000000 --- a/pkg/project/app_classic/dispatcher/docker-compose.yml +++ /dev/null @@ -1,11 +0,0 @@ -version: "3" -services: - dispatcher: - container_name: dispatcher - image: acme/aem-ams/dispatcher-publish:latest - platform: linux/amd64 - ports: - - "80:80" - volumes: - - ./home/docker/httpd/logs:/etc/httpd/logs - - ./home/docker/httpd/cache:/var/www/cache diff --git a/pkg/project/app_classic/local.env b/pkg/project/app_classic/local.env index 722fbf19..ddb631a7 100644 --- a/pkg/project/app_classic/local.env +++ b/pkg/project/app_classic/local.env @@ -14,3 +14,9 @@ AEM_DISPATCHER_IP=127.0.0.1 AEM_DISPATCHER_HTTP_URL=http://${AEM_DISPATCHER_IP} AEM_DISPATCHER_DOMAIN=publish.aem.local AEM_DISPATCHER_DOMAINS=${AEM_DISPATCHER_DOMAIN} author.aem.local + +# Docker/Podman switch + +PODMAN_COMPOSE_WARNING_LOGS=0 +DOCKER_COMMAND=podman +DOCKER_COMPOSE_COMMAND=podman compose diff --git a/pkg/project/app_cloud/Taskfile.yml b/pkg/project/app_cloud/Taskfile.yml index 870f241e..f2b612fd 100644 --- a/pkg/project/app_cloud/Taskfile.yml +++ b/pkg/project/app_cloud/Taskfile.yml @@ -8,6 +8,8 @@ env: AEM_ENV: '{{.AEM_ENV | default "local"}}' AEM_INSTANCE_PROCESSING_MODE: auto AEM_OUTPUT_VALUE: NONE + JAVA_HOME: + sh: sh aemw vendor list --output-value javaHome dotenv: - '.env' # VCS-ignored, user-specific @@ -18,8 +20,7 @@ tasks: init: desc: initialize project cmds: - - sh aemw init - - sh aemw instance init + - sh aemw project init status: desc: check status of AEM instances and dispatcher @@ -187,9 +188,13 @@ tasks: - all/target/*.all-*.zip vars: ARGS: -PfedDev {{.AEM_BUILD_ARGS}} - JAVA_HOME: - sh: sh aemw instance init --output-value javaHome - cmd: JAVA_HOME={{.JAVA_HOME}} mvn clean package {{.ARGS}} + cmd: sh mvnw clean package {{.ARGS}} + + aem:build:version: + desc: check build tool versions + cmds: + - echo "JAVA_HOME=${JAVA_HOME}" + - sh mvnw -version aem:deploy: desc: deploy AEM application @@ -230,19 +235,19 @@ tasks: aliases: [ dispatcher:up ] dir: dispatcher cmds: - - 'command -v docker || { echo "Docker is not installed! Visit: https://www.docker.com/products/docker-desktop"; exit 1; }' + - 'command -v {{.DOCKER_COMMAND}} || { echo "Docker or Podman is not installed!"; exit 1; }' - 'test -d ../aem/home/opt/sdk/dispatcher || { echo "Dispatcher SDK is not available. Try providing AEM SDK ZIP instead of JAR to directory: aem/home/lib"; exit 1; }' - test -d home/sdk || (rm -fr home/sdk && mkdir -p home && cp -r ../aem/home/opt/sdk/dispatcher home/sdk) # - sh home/sdk/bin/validate.sh src > ../aem/home/var/log/dispatcher-validate.log - - docker tag "$(docker load --input "home/sdk/lib/dispatcher-publish-{{ARCH}}.tar.gz" | tail -n 1 | awk -v 'FS= ' '{print $3}')" "adobe/aem-ethos/dispatcher-publish:latest" + - "{{.DOCKER_COMMAND}} tag \"$({{.DOCKER_COMMAND}} load --input 'home/sdk/lib/dispatcher-publish-{{ARCH}}.tar.gz' | tail -n 1 | awk -v 'FS= ' '{print $3}')\" \"adobe/aem-ethos/dispatcher-publish:latest\"" - mkdir -p home/sdk/logs home/sdk/cache - - docker compose up -d + - '{{.DOCKER_COMPOSE_COMMAND}} up -d' dispatcher:stop: desc: stop AEM dispatcher aliases: [ dispatcher:down ] dir: dispatcher - cmd: docker compose down + cmd: '{{.DOCKER_COMPOSE_COMMAND}} down' dispatcher:restart: desc: restart AEM dispatcher @@ -254,19 +259,19 @@ tasks: desc: check status of AEM dispatcher dir: dispatcher cmds: - - docker compose ps - - docker compose logs --tail 20 + - '{{.DOCKER_COMPOSE_COMMAND}} ps' + - '{{.DOCKER_COMPOSE_COMMAND}} logs --tail 20' dispatcher:login: desc: login to AEM dispatcher shell - cmd: docker exec -it dispatcher bash + cmd: '{{.DOCKER_COMMAND}} exec -it dispatcher bash' ignore_error: true dispatcher:destroy: desc: destroy AEM dispatcher dir: dispatcher cmds: - - docker compose rm -fsv + - '{{.DOCKER_COMPOSE_COMMAND}} down -v --remove-orphans' - rm -fr home dispatcher:hosts: diff --git a/pkg/project/app_cloud/aem/default/etc/aem.yml b/pkg/project/app_cloud/aem/default/etc/aem.yml index c75f5270..238cb2f6 100755 --- a/pkg/project/app_cloud/aem/default/etc/aem.yml +++ b/pkg/project/app_cloud/aem/default/etc/aem.yml @@ -78,9 +78,11 @@ instance: timeout: 3s # Bundle state tracking bundle_stable: + skip: false symbolic_names_ignored: [] # OSGi events tracking event_stable: + skip: false # Topics indicating that instance is not stable topics_unstable: - "org/osgi/framework/ServiceEvent/*" @@ -126,18 +128,6 @@ instance: # Archived runtime dir (AEM backup files '*.aemb.zst') backup_dir: "aem/home/var/backup" - # Oak Run tool options (offline instance management) - oak_run: - download_url: "https://repo1.maven.org/maven2/org/apache/jackrabbit/oak-run/1.44.0/oak-run-1.44.0.jar" - store_path: "crx-quickstart/repository/segmentstore" - - # Source files - quickstart: - # AEM SDK ZIP or JAR - dist_file: "aem/home/lib/{aem-sdk,cq-quickstart}-*.{zip,jar}" - # AEM License properties file - license_file: "aem/home/lib/license.properties" - # Status discovery (timezone, AEM version, etc) status: timeout: 500ms @@ -190,7 +180,7 @@ instance: start_level: 20 refresh_packages: true - # Force re-installing of snapshot OSGi bundles (just built / unreleased) + # Force re-uploading/installing of snapshot OSGi bundles (just built / unreleased) snapshot_patterns: [ "**/*-SNAPSHOT.jar" ] snapshot_ignored: false # Use checksums to avoid re-installations when snapshot OSGi bundles are unchanged @@ -209,35 +199,18 @@ instance: replication: bundle_symbolic_name: com.day.cq.cq-replication -java: - # Require following versions before e.g running AEM instances - version_constraints: ">= 11, < 12" - - # Pre-installed local JDK dir - # a) keep it empty to download open source Java automatically for current OS and architecture - # b) set it to absolute path or to env var '[[.Env.JAVA_HOME]]' to indicate where closed source Java like Oracle is installed - home_dir: "" - - # Auto-installed JDK options - download: - # Source URL with template vars support - url: "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.18%2B10/OpenJDK11U-jdk_[[.Arch]]_[[.Os]]_hotspot_11.0.18_10.[[.ArchiveExt]]" - # Map source URL template vars to be compatible with Adoptium Java - replacements: - # Var 'Os' (GOOS) - "darwin": "mac" - # Var 'Arch' (GOARCH) - "x86_64": "x64" - "amd64": "x64" - "386": "x86-32" - # enforce non-ARM Java as some AEM features are not working on ARM (e.g Scene7) - "arm64": "x64" - "aarch64": "x64" - -vault: - "download_url": "https://repo1.maven.org/maven2/org/apache/jackrabbit/vault/vault-cli/3.7.2/vault-cli-3.7.2-bin.tar.gz" + # Workflow Manager + workflow: + launcher: + lib_root: /libs/settings/workflow/launcher + config_root: /conf/global/settings/workflow/launcher + toggle_retry: + timeout: 10m + delay: 10s base: + # Location of library files (AEM SDK ZIP, Quickstart JAR & License, Crypto keys, service packs, additional packages, etc.) + lib_dir: aem/home/lib # Location of temporary files (downloaded AEM packages, etc) tmp_dir: aem/home/tmp # Location of supportive tools (downloaded Java, OakRun, unpacked AEM SDK) @@ -260,7 +233,46 @@ output: # Controls where outputs and logs should be written to when format is 'text' (console|file|both) mode: console -# Content clean options +vendor: + # AEM instance source files + quickstart: + # AEM SDK ZIP or JAR + dist_file: 'aem/home/lib/{aem-sdk,cq-quickstart}-*.{zip,jar}' + # AEM License properties file + license_file: "aem/home/lib/license.properties" + + # JDK used to: run AEM instances, build OSGi bundles, assemble AEM packages + java: + # Require following versions before e.g running AEM instances + version_constraints: ">= 11, < 12" + + # Pre-installed local JDK dir + # a) keep it empty to download open source Java automatically for current OS and architecture + # b) set it to absolute path or to env var '[[.Env.JAVA_HOME]]' to indicate where closed source Java like Oracle is installed + home_dir: "" + + # Auto-installed JDK options + download: + # Source URL with template vars support + url: "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.25%2B9/OpenJDK11U-jdk_[[.Arch]]_[[.Os]]_hotspot_11.0.25_9.[[.ArchiveExt]]" + # Map source URL template vars to be compatible with Adoptium Java + replacements: + # Var 'Os' (GOOS) + "darwin": "mac" + # Var 'Arch' (GOARCH) + "x86_64": "x64" + "amd64": "x64" + "386": "x86-32" + # enforce non-ARM Java as some AEM features are not working on ARM (e.g Scene7) + "arm64": "x64" + "aarch64": "x64" + + # Oak Run tool options (offline instance management) + oak_run: + download_url: "https://repo1.maven.org/maven2/org/apache/jackrabbit/oak-run/1.72.0/oak-run-1.72.0.jar" + store_path: "crx-quickstart/repository/segmentstore" + +# Content-related options content: clean: # File patterns to be deleted diff --git a/pkg/project/app_cloud/dispatcher/docker-compose.yml b/pkg/project/app_cloud/dispatcher/compose.yaml similarity index 77% rename from pkg/project/app_cloud/dispatcher/docker-compose.yml rename to pkg/project/app_cloud/dispatcher/compose.yaml index 79a8eb76..d7cbf1f1 100644 --- a/pkg/project/app_cloud/dispatcher/docker-compose.yml +++ b/pkg/project/app_cloud/dispatcher/compose.yaml @@ -1,4 +1,3 @@ -version: "3" services: dispatcher: container_name: dispatcher @@ -24,3 +23,7 @@ services: # Enable previewing logs and caches directly on host - ./home/sdk/logs:/var/log/apache2 - ./home/sdk/cache:/mnt/var/www + sysctls: + # Fixes: "Permission denied: AH00072: make_sock: could not bind to address [::]:80" + # See: https://documentation.suse.com/smart/container/html/rootless-podman/index.html#rootless-podman-configure-port-below-1024 + net.ipv4.ip_unprivileged_port_start: 0 diff --git a/pkg/project/app_cloud/local.env b/pkg/project/app_cloud/local.env index e0c0f9a1..01941c6d 100644 --- a/pkg/project/app_cloud/local.env +++ b/pkg/project/app_cloud/local.env @@ -14,3 +14,9 @@ AEM_DISPATCHER_IP=127.0.0.1 AEM_DISPATCHER_HTTP_URL=http://${AEM_DISPATCHER_IP} AEM_DISPATCHER_DOMAIN=publish AEM_DISPATCHER_DOMAINS=${AEM_DISPATCHER_DOMAIN} + +# Docker/Podman switch + +PODMAN_COMPOSE_WARNING_LOGS=0 +DOCKER_COMMAND=podman +DOCKER_COMPOSE_COMMAND=podman-compose diff --git a/pkg/project/common/$.mvn/wrapper/maven-wrapper.jar b/pkg/project/common/$.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 00000000..cb28b0e3 Binary files /dev/null and b/pkg/project/common/$.mvn/wrapper/maven-wrapper.jar differ diff --git a/pkg/project/common/$.mvn/wrapper/maven-wrapper.properties b/pkg/project/common/$.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..346d645f --- /dev/null +++ b/pkg/project/common/$.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/pkg/project/common/mvnw b/pkg/project/common/mvnw new file mode 100755 index 00000000..8d937f4c --- /dev/null +++ b/pkg/project/common/mvnw @@ -0,0 +1,308 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.2.0 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "$(uname)" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin ; then + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "\"$javaExecutable\"")" + fi + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$(cd "$wdir/.." || exit 1; pwd) + fi + # end of workaround + done + printf '%s' "$(cd "$basedir" || exit 1; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; + esac + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget > /dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/pkg/project/common/mvnw.cmd b/pkg/project/common/mvnw.cmd new file mode 100644 index 00000000..c4586b56 --- /dev/null +++ b/pkg/project/common/mvnw.cmd @@ -0,0 +1,205 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.2.0 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/pkg/project/common/taskw b/pkg/project/common/taskw index a5235b94..5e72d491 100755 --- a/pkg/project/common/taskw +++ b/pkg/project/common/taskw @@ -122,4 +122,7 @@ export MSYS2_ENV_CONV_EXCL="*" # https://taskfile.dev/api/#env export TASK_COLOR_GREEN=35 +# https://taskfile.dev/experiments/env-precedence +export TASK_X_ENV_PRECEDENCE=1 + "./${BIN_EXEC_FILE}" "$@" diff --git a/pkg/project/constants.go b/pkg/project/constants.go new file mode 100644 index 00000000..ba4a2c12 --- /dev/null +++ b/pkg/project/constants.go @@ -0,0 +1,45 @@ +package project + +import ( + "fmt" + "github.com/samber/lo" +) + +type Kind string + +const ( + KindAuto = "auto" + KindInstance = "instance" + KindAppClassic = "app_classic" + KindAppCloud = "app_cloud" + KindUnknown = "unknown" + + KindPropName = "aemVersion" + KindPropCloudValue = "cloud" + KindPropClassicPrefix = "6." + + GitIgnoreFile = ".gitignore" + PropFile = "archetype.properties" + PackagePropName = "package" +) + +func Kinds() []Kind { + return []Kind{KindInstance, KindAppCloud, KindAppClassic} +} + +func KindStrings() []string { + return lo.Map(Kinds(), func(k Kind, _ int) string { return string(k) }) +} + +func KindOf(name string) (Kind, error) { + if name == KindAuto { + return KindAuto, nil + } else if name == KindInstance { + return KindInstance, nil + } else if name == KindAppCloud { + return KindAppCloud, nil + } else if name == KindAppClassic { + return KindAppClassic, nil + } + return "", fmt.Errorf("project kind '%s' is not supported", name) +} diff --git a/pkg/project/files.go b/pkg/project/files.go new file mode 100644 index 00000000..c9c14b9f --- /dev/null +++ b/pkg/project/files.go @@ -0,0 +1,17 @@ +package project + +import ( + "embed" +) + +//go:embed common +var CommonFiles embed.FS + +//go:embed instance +var InstanceFiles embed.FS + +//go:embed app_classic +var AppClassicFiles embed.FS + +//go:embed app_cloud +var AppCloudFiles embed.FS diff --git a/pkg/project/instance/Taskfile.yml b/pkg/project/instance/Taskfile.yml index 71f03ae3..49a604a2 100644 --- a/pkg/project/instance/Taskfile.yml +++ b/pkg/project/instance/Taskfile.yml @@ -8,6 +8,8 @@ env: AEM_ENV: '{{.AEM_ENV | default "local"}}' AEM_INSTANCE_PROCESSING_MODE: auto AEM_OUTPUT_VALUE: NONE + JAVA_HOME: + sh: sh aemw vendor list --output-value javaHome dotenv: - '.env' # VCS-ignored, user-specific @@ -18,8 +20,7 @@ tasks: init: desc: initialize project cmds: - - sh aemw init - - sh aemw instance init + - sh aemw project init setup: desc: start and provision AEM instances diff --git a/pkg/project/instance/aem/default/etc/aem.yml b/pkg/project/instance/aem/default/etc/aem.yml index fc341018..3cd58084 100755 --- a/pkg/project/instance/aem/default/etc/aem.yml +++ b/pkg/project/instance/aem/default/etc/aem.yml @@ -64,6 +64,8 @@ instance: interval: 6s # Number of successful check attempts that indicates end of checking done_threshold: 4 + # Wait only for those instances whose state has been changed internally (unaware of external changes) + await_strict: true # Max time to wait for the instance to be healthy after executing the start script or e.g deploying a package await_started: timeout: 30m @@ -121,26 +123,11 @@ instance: # Managed locally (set up automatically) local: - # Wait only for those instances whose state has been changed internally (unaware of external changes) - await_strict: true - # Current runtime dir (Sling launchpad, JCR repository) unpack_dir: "aem/home/var/instance" # Archived runtime dir (AEM backup files '*.aemb.zst') backup_dir: "aem/home/var/backup" - # Oak Run tool options (offline instance management) - oak_run: - download_url: "https://repo1.maven.org/maven2/org/apache/jackrabbit/oak-run/1.44.0/oak-run-1.44.0.jar" - store_path: "crx-quickstart/repository/segmentstore" - - # Source files - quickstart: - # AEM SDK ZIP or JAR - dist_file: "aem/home/lib/{aem-sdk,cq-quickstart}-*.{zip,jar}" - # AEM License properties file - license_file: "aem/home/lib/license.properties" - # Status discovery (timezone, AEM version, etc) status: timeout: 500ms @@ -193,7 +180,7 @@ instance: start_level: 20 refresh_packages: true - # Force re-installing of snapshot OSGi bundles (just built / unreleased) + # Force re-uploading/installing of snapshot OSGi bundles (just built / unreleased) snapshot_patterns: [ "**/*-SNAPSHOT.jar" ] snapshot_ignored: false # Use checksums to avoid re-installations when snapshot OSGi bundles are unchanged @@ -221,45 +208,14 @@ instance: timeout: 10m delay: 10s -java: - # Require following versions before e.g running AEM instances - version_constraints: ">= 11, < 12" - - # Pre-installed local JDK dir - # a) keep it empty to download open source Java automatically for current OS and architecture - # b) set it to absolute path or to env var '[[.Env.JAVA_HOME]]' to indicate where closed source Java like Oracle is installed - home_dir: "" - - # Auto-installed JDK options - download: - # Source URL with template vars support - url: "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.18%2B10/OpenJDK11U-jdk_[[.Arch]]_[[.Os]]_hotspot_11.0.18_10.[[.ArchiveExt]]" - # Map source URL template vars to be compatible with Adoptium Java - replacements: - # Var 'Os' (GOOS) - "darwin": "mac" - # Var 'Arch' (GOARCH) - "x86_64": "x64" - "amd64": "x64" - "386": "x86-32" - # enforce non-ARM Java as some AEM features are not working on ARM (e.g Scene7) - "arm64": "x64" - "aarch64": "x64" - -vault: - "download_url": "https://repo1.maven.org/maven2/org/apache/jackrabbit/vault/vault-cli/3.7.2/vault-cli-3.7.2-bin.tar.gz" - base: + # Location of library files (AEM SDK ZIP, Quickstart JAR & License, Crypto keys, service packs, additional packages, etc.) + lib_dir: aem/home/lib # Location of temporary files (downloaded AEM packages, etc) tmp_dir: aem/home/tmp # Location of supportive tools (downloaded Java, OakRun, unpacked AEM SDK) tool_dir: aem/home/opt -log: - level: info - timestamp_format: "2006-01-02 15:04:05" - full_timestamp: true - input: format: yml file: STDIN @@ -272,7 +228,46 @@ output: # Controls where outputs and logs should be written to when format is 'text' (console|file|both) mode: console -# Content clean options +vendor: + # AEM instance source files + quickstart: + # AEM SDK ZIP or JAR + dist_file: 'aem/home/lib/{aem-sdk,cq-quickstart}-*.{zip,jar}' + # AEM License properties file + license_file: "aem/home/lib/license.properties" + + # JDK used to: run AEM instances, build OSGi bundles, assemble AEM packages + java: + # Require following versions before e.g running AEM instances + version_constraints: ">= 11, < 12" + + # Pre-installed local JDK dir + # a) keep it empty to download open source Java automatically for current OS and architecture + # b) set it to absolute path or to env var '[[.Env.JAVA_HOME]]' to indicate where closed source Java like Oracle is installed + home_dir: "" + + # Auto-installed JDK options + download: + # Source URL with template vars support + url: "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.25%2B9/OpenJDK11U-jdk_[[.Arch]]_[[.Os]]_hotspot_11.0.25_9.[[.ArchiveExt]]" + # Map source URL template vars to be compatible with Adoptium Java + replacements: + # Var 'Os' (GOOS) + "darwin": "mac" + # Var 'Arch' (GOARCH) + "x86_64": "x64" + "amd64": "x64" + "386": "x86-32" + # enforce non-ARM Java as some AEM features are not working on ARM (e.g Scene7) + "arm64": "x64" + "aarch64": "x64" + + # Oak Run tool options (offline instance management) + oak_run: + download_url: "https://repo1.maven.org/maven2/org/apache/jackrabbit/oak-run/1.72.0/oak-run-1.72.0.jar" + store_path: "crx-quickstart/repository/segmentstore" + +# Content-related options content: clean: # File patterns to be deleted diff --git a/pkg/project/project.go b/pkg/project/project.go deleted file mode 100644 index 61d4ddbf..00000000 --- a/pkg/project/project.go +++ /dev/null @@ -1,286 +0,0 @@ -package project - -import ( - "embed" - "fmt" - "github.com/magiconair/properties" - "github.com/samber/lo" - log "github.com/sirupsen/logrus" - "github.com/wttech/aemc/pkg/cfg" - "github.com/wttech/aemc/pkg/common" - "github.com/wttech/aemc/pkg/common/filex" - "github.com/wttech/aemc/pkg/common/osx" - "github.com/wttech/aemc/pkg/common/pathx" - "io/fs" - "strings" -) - -type Project struct { - config *cfg.Config -} - -func New(config *cfg.Config) *Project { - return &Project{config} -} - -type Kind string - -const ( - KindAuto = "auto" - KindInstance = "instance" - KindAppClassic = "app_classic" - KindAppCloud = "app_cloud" - KindUnknown = "unknown" - - KindPropName = "aemVersion" - KindPropCloudValue = "cloud" - KindPropClassicPrefix = "6." - - GitIgnoreFile = ".gitignore" - PropFile = "archetype.properties" - PackagePropName = "package" -) - -func Kinds() []Kind { - return []Kind{KindInstance, KindAppCloud, KindAppClassic} -} - -func KindStrings() []string { - return lo.Map(Kinds(), func(k Kind, _ int) string { return string(k) }) -} - -func KindOf(name string) (Kind, error) { - if name == KindAuto { - return KindAuto, nil - } else if name == KindInstance { - return KindInstance, nil - } else if name == KindAppCloud { - return KindAppCloud, nil - } else if name == KindAppClassic { - return KindAppClassic, nil - } - return "", fmt.Errorf("project kind '%s' is not supported", name) -} - -func (p Project) IsAppKind(kind Kind) bool { - return kind == KindAppClassic || kind == KindAppCloud -} - -//go:embed common -var commonFiles embed.FS - -//go:embed instance -var instanceFiles embed.FS - -//go:embed app_classic -var appClassicFiles embed.FS - -//go:embed app_cloud -var appCloudFiles embed.FS - -func (p Project) InitializeWithChanged(kind Kind) (bool, error) { - if p.config.TemplateFileExists() { - return false, nil - } - if err := p.initialize(kind); err != nil { - return false, err - } - return true, nil -} - -func (p Project) initialize(kind Kind) error { - if err := p.prepareDefaultFiles(kind); err != nil { - return err - } - if err := p.prepareGitIgnore(kind); err != nil { - return err - } - if err := p.prepareLocalEnvFile(kind); err != nil { - return err - } - return nil -} - -func (p Project) prepareDefaultFiles(kind Kind) error { - log.Infof("preparing default files for project of kind '%s'", kind) - switch kind { - case KindInstance: - if err := copyEmbedFiles(&commonFiles, "common/"); err != nil { - return err - } - if err := copyEmbedFiles(&instanceFiles, "instance/"); err != nil { - return err - } - case KindAppClassic: - if err := copyEmbedFiles(&commonFiles, "common/"); err != nil { - return err - } - if err := copyEmbedFiles(&appClassicFiles, "app_classic/"); err != nil { - return err - } - case KindAppCloud: - if err := copyEmbedFiles(&commonFiles, "common/"); err != nil { - return err - } - if err := copyEmbedFiles(&appCloudFiles, "app_cloud/"); err != nil { - return err - } - default: - return fmt.Errorf("project kind '%s' cannot be initialized", kind) - } - return nil -} - -func copyEmbedFiles(efs *embed.FS, dirPrefix string) error { - return fs.WalkDir(efs, ".", func(path string, d fs.DirEntry, err error) error { - if d.IsDir() { - return nil - } - bytes, err := efs.ReadFile(path) - if err != nil { - return err - } - if err := filex.Write(strings.TrimPrefix(path, dirPrefix), bytes); err != nil { - return err - } - return nil - }) -} - -// need to be in sync with osx.EnvVarsLoad() -func (p Project) prepareGitIgnore(kind Kind) error { - switch kind { - case KindAppClassic, KindAppCloud: - return filex.AppendString(GitIgnoreFile, osx.LineSep()+strings.Join([]string{ - "", - "# " + common.AppName, - common.HomeDir + "/", - common.DispatcherHomeDir + "/", - ".task/", - "." + osx.EnvFileExt, - "." + osx.EnvFileExt + ".*", - "", - }, osx.LineSep())) - default: - return filex.AppendString(GitIgnoreFile, osx.LineSep()+strings.Join([]string{ - "", - "# " + common.AppName, - common.HomeDir + "/", - ".task/", - "." + osx.EnvFileExt, - "." + osx.EnvFileExt + ".*", - "", - }, osx.LineSep())) - } -} - -func (p Project) prepareLocalEnvFile(kind Kind) error { - if p.IsAppKind(kind) && p.HasProps() { - prop, err := p.Prop(PackagePropName) - if err != nil { - return err - } - propTrimmed := strings.TrimSpace(prop) - if propTrimmed != "" { - if err := filex.AppendString(osx.EnvLocalFile, osx.LineSep()+strings.Join([]string{ - "", - "AEM_PACKAGE=" + prop, - "", - }, osx.LineSep())); err != nil { - return err - } - } - } - return nil -} - -func (p Project) KindDetermine(name string) (Kind, error) { - var kind Kind = KindAuto - if name != "" { - kindCandidate, err := KindOf(name) - if err != nil { - return "", err - } - kind = kindCandidate - } - if kind == KindAuto { - kindCandidate, err := p.KindInfer() - if err != nil { - return "", err - } - kind = kindCandidate - } - return kind, nil -} - -func (p Project) EnsureDirs() error { - log.Infof("ensuring conventional project directories") - if err := pathx.Ensure(common.LibDir); err != nil { - return err - } - if err := pathx.Ensure(common.TmpDir); err != nil { - return err - } - return nil -} - -func (p Project) HasProps() bool { - return pathx.Exists(PropFile) -} - -func (p Project) Prop(name string) (string, error) { - propLoader := properties.Loader{ - Encoding: properties.ISO_8859_1, - DisableExpansion: true, - } - props, err := propLoader.LoadFile(PropFile) - if err != nil { - return "", fmt.Errorf("cannot read project property '%s' from file '%s': %w", name, PropFile, err) - } - propValue := props.GetString(name, "") - return propValue, nil -} - -func (p Project) KindInfer() (Kind, error) { - if p.HasProps() { - log.Infof("inferring project kind basing on file '%s' and property '%s'", PropFile, KindPropName) - propValue, err := p.Prop(KindPropName) - if err != nil { - return "", err - } - var kind Kind - if propValue == KindPropCloudValue { - kind = KindAppCloud - } else if strings.HasPrefix(propValue, KindPropClassicPrefix) { - kind = KindAppClassic - } else { - return "", fmt.Errorf("cannot infer project kind as value '%s' of property '%s' in file '%s' is not recognized", propValue, KindPropName, PropFile) - } - log.Infof("inferred project kind basing on file '%s' and property '%s' is '%s'", PropFile, KindPropName, kind) - return kind, nil - } - return KindUnknown, nil -} - -func (p Project) GettingStarted(kind Kind) (string, error) { - dirsIgnored := []string{common.HomeDir} - if kind == KindAppCloud || kind == KindAppClassic { - dirsIgnored = []string{common.HomeDir, common.DispatcherHomeDir} - } - text := fmt.Sprintf(strings.Join([]string{ - "As a next step provide AEM files (JAR or SDK ZIP, license, service packs) to directory '" + common.LibDir + "'.", - "Alternatively, instruct the tool where these files are located by adjusting properties: 'dist_file', 'license_file' in configuration file '" + cfg.FileDefault + "'.", - "", - fmt.Sprintf("Make sure to exclude the directories from VCS versioning and IDE indexing: %s", strings.Join(dirsIgnored, ", ")), - "", - "Finally, use tasks to manage AEM instances and more:", - "", - "sh taskw --list", - "", - "It is also possible to run individual AEM Compose CLI commands separately.", - "Discover available commands by running:", - "", - "sh aemw --help", - }, "\n")) - return text, nil -} diff --git a/pkg/sdk.go b/pkg/sdk.go index 49da480d..2a460cf9 100644 --- a/pkg/sdk.go +++ b/pkg/sdk.go @@ -10,16 +10,16 @@ import ( "path/filepath" ) -func NewSDK(localOpts *LocalOpts) *SDK { - return &SDK{localOpts: localOpts} +func NewSDK(vendorManager *VendorManager) *SDK { + return &SDK{vendorManager: vendorManager} } type SDK struct { - localOpts *LocalOpts + vendorManager *VendorManager } func (s SDK) Dir() string { - return fmt.Sprintf("%s/%s", s.localOpts.manager.aem.baseOpts.ToolDir, "sdk") + return fmt.Sprintf("%s/%s", s.vendorManager.aem.baseOpts.ToolDir, "sdk") } type SDKLock struct { @@ -32,38 +32,38 @@ func (s SDK) lock(zipFile string) osx.Lock[SDKLock] { }) } -func (s SDK) Prepare() error { - zipFile, err := s.localOpts.Quickstart.FindDistFile() +func (s SDK) PrepareWithChanged() (bool, error) { + zipFile, err := s.vendorManager.quickstart.FindDistFile() if err != nil { - return err + return false, err } lock := s.lock(zipFile) check, err := lock.State() if err != nil { - return err + return false, err } if check.UpToDate { log.Debugf("existing SDK '%s' is up-to-date", zipFile) - return nil + return false, nil } log.Infof("preparing new SDK '%s'", zipFile) err = s.prepare(zipFile) if err != nil { - return err + return false, err } err = lock.Lock() if err != nil { - return err + return false, err } log.Infof("prepared new SDK '%s'", zipFile) jar, err := s.QuickstartJar() if err != nil { - return err + return false, err } log.Debugf("found JAR '%s' in unpacked SDK '%s'", jar, zipFile) - return nil + return true, nil } func (s SDK) prepare(zipFile string) error { @@ -136,7 +136,7 @@ func (s SDK) unpackDispatcher() error { } log.Infof("unpacking SDK dispatcher tools using script '%s' to dir '%s'", script, s.DispatcherDir()) cmd := execx.CommandShell([]string{script, "--target", s.DispatcherDir()}) - s.localOpts.manager.aem.CommandOutput(cmd) + s.vendorManager.aem.CommandOutput(cmd) if err := cmd.Run(); err != nil { return fmt.Errorf("cannot run SDK dispatcher tools unpacking script '%s': %w", script, err) } diff --git a/pkg/vault_cli.go b/pkg/vault_cli.go deleted file mode 100644 index 51420888..00000000 --- a/pkg/vault_cli.go +++ /dev/null @@ -1,112 +0,0 @@ -package pkg - -import ( - "fmt" - log "github.com/sirupsen/logrus" - "github.com/wttech/aemc/pkg/common/execx" - "github.com/wttech/aemc/pkg/common/filex" - "github.com/wttech/aemc/pkg/common/httpx" - "github.com/wttech/aemc/pkg/common/osx" - "github.com/wttech/aemc/pkg/common/pathx" - "os" - "path/filepath" - "strings" -) - -func NewVaultCli(aem *AEM) *VaultCli { - cv := aem.baseOpts.Config().Values() - - return &VaultCli{ - aem: aem, - - DownloadURL: cv.GetString("vault.download_url"), - } -} - -type VaultCli struct { - aem *AEM - - DownloadURL string -} - -type VaultCliLock struct { - DownloadURL string `yaml:"download_url"` -} - -func (v VaultCli) dir() string { - if v.aem.Detached() { - return filepath.Join(os.TempDir(), "vault-cli") - } - return filepath.Join(v.aem.baseOpts.ToolDir, "vault-cli") -} - -func (v VaultCli) execFile() string { - vaultDir, _, _ := strings.Cut(filepath.Base(v.DownloadURL), "-bin") - execDir := filepath.Join(v.dir(), vaultDir, "bin") - if osx.IsWindows() { - return pathx.Canonical(execDir + "/vlt.bat") - } - return pathx.Canonical(execDir + "/vlt") -} - -func (v VaultCli) lock() osx.Lock[VaultCliLock] { - return osx.NewLock(v.dir()+"/lock/create.yml", func() (VaultCliLock, error) { return VaultCliLock{DownloadURL: v.DownloadURL}, nil }) -} - -func (v VaultCli) Prepare() error { - lock := v.lock() - check, err := lock.State() - if err != nil { - return err - } - if check.UpToDate { - log.Debugf("existing Vault '%s' is up-to-date", v.DownloadURL) - return nil - } - log.Infof("preparing new Vault '%s'", v.DownloadURL) - if err = v.prepare(); err != nil { - return err - } - if err = lock.Lock(); err != nil { - return err - } - log.Infof("prepared new Vault '%s'", v.DownloadURL) - - return nil -} - -func (v VaultCli) archiveFile() string { - return pathx.Canonical(fmt.Sprintf("%s/%s", v.dir(), filepath.Base(v.DownloadURL))) -} - -func (v VaultCli) prepare() error { - if err := pathx.DeleteIfExists(v.dir()); err != nil { - return err - } - archiveFile := v.archiveFile() - log.Infof("downloading Vault from URL '%s' to file '%s'", v.DownloadURL, archiveFile) - if err := httpx.DownloadOnce(v.DownloadURL, archiveFile); err != nil { - return err - } - log.Infof("downloaded Vault from URL '%s' to file '%s'", v.DownloadURL, archiveFile) - - log.Infof("unarchiving Vault from file '%s'", archiveFile) - if err := filex.Unarchive(archiveFile, v.dir()); err != nil { - return err - } - log.Infof("unarchived Vault from file '%s'", archiveFile) - return nil -} - -func (v VaultCli) CommandShell(args []string) error { - if err := v.Prepare(); err != nil { - return fmt.Errorf("cannot prepare Vault before running command: %w", err) - } - vaultCliArgs := append([]string{v.execFile()}, args...) - cmd := execx.CommandShell(vaultCliArgs) - v.aem.CommandOutput(cmd) - if err := cmd.Run(); err != nil { - return fmt.Errorf("cannot run Vault command: %w", err) - } - return nil -} diff --git a/pkg/vendor_manager.go b/pkg/vendor_manager.go new file mode 100644 index 00000000..2be9d19a --- /dev/null +++ b/pkg/vendor_manager.go @@ -0,0 +1,77 @@ +package pkg + +// VendorManager manages third-party tools like JDK, OakRun, and Vault CLI +type VendorManager struct { + aem *AEM + + javaManager *JavaManager + oakRun *OakRun + quickstart *Quickstart + sdk *SDK +} + +func NewVendorManager(aem *AEM) *VendorManager { + result := &VendorManager{aem: aem} + result.javaManager = NewJavaManager(result) + result.sdk = NewSDK(result) + result.quickstart = NewQuickstart(result) + result.oakRun = NewOakRun(result) + return result +} + +func (vm *VendorManager) InstanceJar() (string, error) { + sdk, err := vm.quickstart.IsDistSDK() + if err != nil { + return "", err + } + if sdk { + return vm.sdk.QuickstartJar() + } + return vm.quickstart.FindDistFile() +} + +func (vm *VendorManager) PrepareWithChanged() (bool, error) { + changed := false + + javaChanged, err := vm.javaManager.PrepareWithChanged() + changed = changed || javaChanged + if err != nil { + return changed, err + } + + sdk, err := vm.quickstart.IsDistSDK() + if err != nil { + return false, err + } + if sdk { + sdkChanged, err := vm.sdk.PrepareWithChanged() + changed = changed || sdkChanged + if err != nil { + return changed, err + } + } + + oakRunChanged, err := vm.oakRun.PrepareWithChanged() + changed = changed || oakRunChanged + if err != nil { + return changed, err + } + + return changed, nil +} + +func (vm *VendorManager) JavaManager() *JavaManager { + return vm.javaManager +} + +func (vm *VendorManager) OakRun() *OakRun { + return vm.oakRun +} + +func (vm *VendorManager) Quickstart() *Quickstart { + return vm.quickstart +} + +func (vm *VendorManager) SDK() *SDK { + return vm.sdk +} diff --git a/project-init.sh b/project-install.sh similarity index 75% rename from project-init.sh rename to project-install.sh index 8daadd03..479a2b0c 100644 --- a/project-init.sh +++ b/project-install.sh @@ -19,7 +19,7 @@ chmod +x "${AEM_WRAPPER}" sh ${AEM_WRAPPER} version echo "" -echo "Success! Now initialize the project files by running command below:" +echo "Success! Now scaffold the AEM Compose files in the project by running command below:" echo "" -echo "sh ${AEM_WRAPPER} init" +echo "sh ${AEM_WRAPPER} project scaffold"