From 5916725e57a44af8a6d0d35348afd913e687b6f3 Mon Sep 17 00:00:00 2001 From: Dominik Przybyl Date: Tue, 28 May 2024 08:59:12 +0200 Subject: [PATCH 1/3] added local type --- examples/local/aem.tf | 46 +++++ examples/local/aem.yml | 258 +++++++++++++++++++++++++++ examples/local/main.tf | 10 ++ internal/client/client_manager.go | 2 + internal/client/connection_local.go | 46 +++++ internal/provider/instance_client.go | 8 + 6 files changed, 370 insertions(+) create mode 100644 examples/local/aem.tf create mode 100644 examples/local/aem.yml create mode 100644 examples/local/main.tf create mode 100644 internal/client/connection_local.go diff --git a/examples/local/aem.tf b/examples/local/aem.tf new file mode 100644 index 0000000..1badf40 --- /dev/null +++ b/examples/local/aem.tf @@ -0,0 +1,46 @@ +resource "aem_instance" "single" { + client { + type = "local" + settings = { + } + } + + system { + data_dir = local.compose_dir + work_dir = local.work_dir + bootstrap = { + inline = [ + ] + } + } + + compose { + config = file("aem.yml") + create = { + inline = [ + "mkdir -p ${local.compose_dir}/aem/home/lib", + "cp ${local.library_dir}/* ${local.compose_dir}/aem/home/lib", + "sh aemw instance init", + "sh aemw instance create", + ] + } + configure = { + inline = [ + "sh aemw osgi config save --pid 'org.apache.sling.jcr.davex.impl.servlets.SlingDavExServlet' --input-string 'alias: /crx/server'", + "sh aemw repl agent setup -A --location 'author' --name 'publish' --input-string '{enabled: true, transportUri: \"http://localhost:4503/bin/receive?sling:authRequestLogin=1\", transportUser: admin, transportPassword: admin, userId: admin}'", + ] + } + } +} + +locals { + env = "local" + data_dir = "~/data/${local.env}" + compose_dir = "${local.data_dir}/aemc" + work_dir = "~/tmp/${local.env}/aemc" + library_dir = "~/lib" +} + +output "aem_instances" { + value = "127.0.0.1" +} diff --git a/examples/local/aem.yml b/examples/local/aem.yml new file mode 100644 index 0000000..197db8d --- /dev/null +++ b/examples/local/aem.yml @@ -0,0 +1,258 @@ +# AEM instances to work with +instance: + + # Full details of local or remote instances + config: + local_author: + active: true + http_url: http://127.0.0.1:6502 + user: admin + password: admin + run_modes: [local] + jvm_opts: + - -server + - -Djava.awt.headless=true + - -Djava.io.tmpdir=[[canonicalPath .Path "aem/home/tmp"]] + - -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:16502 + - -Duser.language=en + - -Duser.country=US + - -Duser.timezone=UTC + start_opts: [] + secret_vars: + - ACME_SECRET=value + env_vars: + - ACME_VAR=value + sling_props: [] + local_publish: + active: true + http_url: http://127.0.0.1:6503 + user: admin + password: admin + run_modes: [local] + jvm_opts: + - -server + - -Djava.awt.headless=true + - -Djava.io.tmpdir=[[canonicalPath .Path "aem/home/tmp"]] + - -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:16503 + - -Duser.language=en + - -Duser.country=US + - -Duser.timezone=UTC + start_opts: [] + secret_vars: + - ACME_SECRET=value + env_vars: + - ACME_VAR=value + sling_props: [] + + # Tuning performance & reliability + # 'auto' - for more than 1 local instances - 'serial', otherwise 'parallel' + # 'parallel' - for working with remote instances + # 'serial' - for working with local instances + processing_mode: auto + + # HTTP client settings + http: + timeout: 10m + debug: false + disable_warn: true + + # State checking + check: + # Time to wait before first state checking (to avoid false-positives) + warmup: 1s + # Time to wait for next state checking + interval: 6s + # Number of successful check attempts that indicates end of checking + done_threshold: 3 + # 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 + # Max time to wait for the instance to be stopped after executing the stop script + await_stopped: + timeout: 10m + # Max time in which socket connection to instance should be established + reachable: + skip: false + 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/*" + - "org/osgi/framework/FrameworkEvent/*" + - "org/osgi/framework/BundleEvent/*" + # Ignored service names to handle known issues + details_ignored: + - "*.*MBean" + - "org.osgi.service.component.runtime.ServiceComponentRuntime" + - "java.util.ResourceBundle" + received_max_age: 5s + # OSGi components state tracking + component_stable: + skip: false + pids: + include: ['com.day.crx.packaging.*', 'org.apache.sling.installer.*'] + exclude: ['org.apache.sling.installer.hc.*', 'org.apache.sling.installer.core.impl.console.*'] + match: + "disabled": [] + "no config": [] + "unsatisfied (reference)": [] + "satisfied": [] + # Sling Installer tracking + installer: + skip: false + # JMX state checking + state: true + # Pause Installation nodes checking + pause: true + # Specific endpoints / paths (like login page) + path_ready: + timeout: 10s + login_page: + skip: false + path: "/libs/granite/core/content/login.html" + status_code: 200 + contained_text: QUICKSTART_HOMEPAGE + + # Managed locally (set up automatically) + local: + # 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 + + # JCR Repository + repo: + property_change_ignored: + # AEM assigns them automatically + - "jcr:created" + - "cq:lastModified" + # AEM encrypts it right after changing by replication agent setup command + - "transportPassword" + + # CRX Package Manager + package: + # Force re-uploading/installing of snapshot AEM packages (just built / unreleased) + snapshot_patterns: [ "**/*-SNAPSHOT.zip" ] + snapshot_ignored: false + # Use checksums to avoid re-deployments when snapshot AEM packages are unchanged + snapshot_deploy_skipping: true + # Disable following workflow launchers for a package deployment time only + toggled_workflows: [/libs/settings/workflow/launcher/config/update_asset_*,/libs/settings/workflow/launcher/config/dam_*] + # Also sub-packages + install_recursive: true + # Use slower HTML endpoint for deployments but with better troubleshooting + install_html: + enabled: false + # Print HTML directly to console instead of writing to file + console: false + # Fail on case 'installed with errors' + strict: true + # Number of changes after which the commit to the repository is performed + install_save_threshold: 1024 + # Allows to relax dependency handling if needed + install_dependency_handling: required + # Controls how 'rep:policy' nodes are handled during import + install_ac_handling: '' + + # 'SSL By Default' + ssl: + setup_timeout: 30s + + # OSGi Framework + osgi: + shutdown_delay: 3s + + bundle: + install: + start: true + start_level: 20 + refresh_packages: true + + # Crypto Support + crypto: + key_bundle_symbolic_name: com.adobe.granite.crypto.file + + # Replication + replication: + bundle_symbolic_name: com.day.cq.cq-replication + + # Workflow Manager + workflow: + launcher: + lib_root: /libs/settings/workflow/launcher + config_root: /conf/global/settings/workflow/launcher + toggle_retry: + 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" + +base: + # 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 + +output: + format: text + log: + # File path of logs written especially when output format is different than 'text' + file: aem/home/var/log/aem.log + # Controls where outputs and logs should be written to when format is 'text' (console|file|both) + mode: both diff --git a/examples/local/main.tf b/examples/local/main.tf new file mode 100644 index 0000000..44c4611 --- /dev/null +++ b/examples/local/main.tf @@ -0,0 +1,10 @@ +terraform { + required_providers { + aem = { + source = "registry.terraform.io/wttech/aem" + version = "< 2.0.0" + } + } +} + +provider "aem" {} diff --git a/internal/client/client_manager.go b/internal/client/client_manager.go index 8f37b5d..02d69bb 100644 --- a/internal/client/client_manager.go +++ b/internal/client/client_manager.go @@ -48,6 +48,8 @@ func (c ClientManager) connection(typeName string, settings map[string]string) ( commandWaitMin: cast.ToDuration(settings["command_wait_min"]), commandWaitMax: cast.ToDuration(settings["command_wait_max"]), }, nil + case "local": + return &LocalConnection{}, nil } return nil, fmt.Errorf("unknown AEM client type: %s", typeName) } diff --git a/internal/client/connection_local.go b/internal/client/connection_local.go new file mode 100644 index 0000000..7637417 --- /dev/null +++ b/internal/client/connection_local.go @@ -0,0 +1,46 @@ +package client + +import ( + "fmt" + "os/exec" + "strings" +) + +type LocalConnection struct { +} + +func (a *LocalConnection) Info() string { + return "local environment" +} + +func (a *LocalConnection) User() string { + return "" +} + +func (a *LocalConnection) Connect() error { + return nil +} + +func (a *LocalConnection) Disconnect() error { + return nil +} + +func (a *LocalConnection) Command(cmdLine []string) ([]byte, error) { + var alterCmdLine []string + for _, cmdElem := range cmdLine { + alterCmdLine = append(alterCmdLine, strings.Trim(cmdElem, `"`)) + } + cmd := exec.Command(alterCmdLine[0], alterCmdLine[1:]...) + output, err := cmd.Output() + if err != nil { + return nil, fmt.Errorf("local: error executing command: %v", err) + } + return output, nil +} + +func (a *LocalConnection) CopyFile(localPath string, remotePath string) error { + cmd := fmt.Sprintf("cp %s %s", localPath, remotePath) + cmdLine := []string{"sh", "-c", "\"" + cmd + "\""} + _, err := a.Command(cmdLine) + return err +} diff --git a/internal/provider/instance_client.go b/internal/provider/instance_client.go index fab7aa9..dffcb05 100644 --- a/internal/provider/instance_client.go +++ b/internal/provider/instance_client.go @@ -106,6 +106,10 @@ func (ic *InstanceClient) saveProfileScript() error { } func (ic *InstanceClient) configureService() error { + if ic.data.Client.Type.ValueString() == "local" { + return nil + } + user := ic.data.System.User.ValueString() if user == "" { user = ic.cl.Connection().User() @@ -134,6 +138,10 @@ func (ic *InstanceClient) configureService() error { } func (ic *InstanceClient) runServiceAction(action string) error { + if ic.data.Client.Type.ValueString() == "local" { + return nil + } + ic.cl.Sudo = true defer func() { ic.cl.Sudo = false }() From d0c7483d5d3cf9d6df2de110cbc5093c2ffe3a61 Mon Sep 17 00:00:00 2001 From: Dominik Przybyl Date: Sat, 15 Jun 2024 13:18:01 +0200 Subject: [PATCH 2/3] added local type --- docs/resources/instance.md | 1 + examples/aws_ssm/aws.tf | 24 ++++++++++++------------ examples/local/aem.tf | 7 ++++--- internal/provider/instance_client.go | 4 ++-- internal/provider/instance_model.go | 19 +++++++++++++------ 5 files changed, 32 insertions(+), 23 deletions(-) diff --git a/docs/resources/instance.md b/docs/resources/instance.md index a4eb6fc..612beaa 100644 --- a/docs/resources/instance.md +++ b/docs/resources/instance.md @@ -101,6 +101,7 @@ Optional: - `bootstrap` (Attributes) Script executed once upon instance connection, often for mounting on VM data volumes from attached disks (e.g., AWS EBS, Azure Disk Storage). This script runs only once, even during instance recreation, as changes are typically persistent and system-wide. If re-execution is needed, it is recommended to set up a new machine. (see [below for nested schema](#nestedatt--system--bootstrap)) - `data_dir` (String) Remote root path in which AEM Compose files and unpacked AEM instances will be stored. - `env` (Map of String) Environment variables for AEM instances. +- `service_enabled` (Boolean) Enabled the AEM system service (systemd). - `service_config` (String) Contents of the AEM system service definition file (systemd). - `user` (String) System user under which AEM instance will be running. By default, the same as the user used to connect to the machine. - `work_dir` (String) Remote root path where provider-related files will be stored. diff --git a/examples/aws_ssm/aws.tf b/examples/aws_ssm/aws.tf index 76472af..1ee3150 100644 --- a/examples/aws_ssm/aws.tf +++ b/examples/aws_ssm/aws.tf @@ -3,10 +3,10 @@ resource "aws_instance" "aem_single" { instance_type = "m5.xlarge" iam_instance_profile = aws_iam_instance_profile.aem_ec2.name tags = local.tags - user_data = trimspace(< Date: Mon, 17 Jun 2024 09:56:04 +0200 Subject: [PATCH 3/3] added local type (service name) --- internal/provider/instance_client.go | 17 ++++++++++++----- internal/provider/instance_model.go | 7 +++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/internal/provider/instance_client.go b/internal/provider/instance_client.go index e716300..25882d2 100644 --- a/internal/provider/instance_client.go +++ b/internal/provider/instance_client.go @@ -86,8 +86,15 @@ func (ic *InstanceClient) create() error { return nil } +func (ic *InstanceClient) serviceName() string { + if ic.data.System.ServiceName.ValueString() != "" { + return ic.data.System.ServiceName.ValueString() + } + return ServiceName +} + func (ic *InstanceClient) saveProfileScript() error { - envFile := fmt.Sprintf("/etc/profile.d/%s.sh", ServiceName) + envFile := fmt.Sprintf("/etc/profile.d/%s.sh", ic.serviceName()) var systemEnvMap map[string]string ic.data.System.Env.ElementsAs(ic.ctx, &systemEnvMap, true) @@ -106,7 +113,7 @@ func (ic *InstanceClient) saveProfileScript() error { } func (ic *InstanceClient) configureService() error { - if !ic.data.System.ServiceEnabled.ValueBool() || ic.data.Client.Type.ValueString() == "local" { + if !ic.data.System.ServiceEnabled.ValueBool() { return nil } @@ -126,7 +133,7 @@ func (ic *InstanceClient) configureService() error { if err != nil { return fmt.Errorf("unable to template AEM system service definition: %w", err) } - serviceFile := fmt.Sprintf("/etc/systemd/system/%s.service", ServiceName) + serviceFile := fmt.Sprintf("/etc/systemd/system/%s.service", ic.serviceName()) if err := ic.cl.FileWrite(serviceFile, serviceTemplated); err != nil { return fmt.Errorf("unable to write AEM system service definition '%s': %w", serviceFile, err) } @@ -138,14 +145,14 @@ func (ic *InstanceClient) configureService() error { } func (ic *InstanceClient) runServiceAction(action string) error { - if !ic.data.System.ServiceEnabled.ValueBool() || ic.data.Client.Type.ValueString() == "local" { + if !ic.data.System.ServiceEnabled.ValueBool() { return nil } ic.cl.Sudo = true defer func() { ic.cl.Sudo = false }() - outBytes, err := ic.cl.RunShellCommand(fmt.Sprintf("systemctl %s %s.service", action, ServiceName), ".") + outBytes, err := ic.cl.RunShellCommand(fmt.Sprintf("systemctl %s %s.service", action, ic.serviceName()), ".") if err != nil { return fmt.Errorf("unable to perform AEM system service action '%s': %w", action, err) } diff --git a/internal/provider/instance_model.go b/internal/provider/instance_model.go index 7e2b08a..e0cc979 100644 --- a/internal/provider/instance_model.go +++ b/internal/provider/instance_model.go @@ -34,6 +34,7 @@ type InstanceResourceModel struct { WorkDir types.String `tfsdk:"work_dir"` Env types.Map `tfsdk:"env"` ServiceEnabled types.Bool `tfsdk:"service_enabled"` + ServiceName types.String `tfsdk:"service_name"` ServiceConfig types.String `tfsdk:"service_config"` User types.String `tfsdk:"user"` Bootstrap InstanceScript `tfsdk:"bootstrap"` @@ -176,6 +177,12 @@ func (r *InstanceResource) Schema(ctx context.Context, req resource.SchemaReques Computed: true, Default: booldefault.StaticBool(true), }, + "service_name": schema.StringAttribute{ + MarkdownDescription: "Name of the AEM system service (systemd).", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, "service_config": schema.StringAttribute{ MarkdownDescription: "Contents of the AEM system service definition file (systemd).", Optional: true,