Skip to content

Commit

Permalink
Merge pull request #17 from wttech/system-service
Browse files Browse the repository at this point in the history
System service PoC
  • Loading branch information
krystian-panek-vmltech authored Nov 10, 2023
2 parents 917dc09 + d865c2d commit 0d429cd
Show file tree
Hide file tree
Showing 17 changed files with 388 additions and 238 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Built on top of [AEM Compose](https://github.com/wttech/aemc).

```hcl
resource "aem_instance" "single" {
depends_on = [aws_instance.aem_single, aws_volume_attachment.aem_single_data]
depends_on = [aws_instance.aem_single, aws_volume_attachment.aem_single_data]§
client {
type = "ssh"
Expand Down
31 changes: 7 additions & 24 deletions examples/ssh/aem.tf
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,10 @@ resource "aem_instance" "single" {
private_key_file = local.ssh_private_key # cannot be put into state as this is OS-dependent
}
}
compose {
version = "1.5.8"
data_dir = local.aem_single_compose_dir
}
hook {
bootstrap = <<EOF

system {
data_dir = local.aem_single_compose_dir
bootstrap_script = <<SHELL
#!/bin/sh
(
echo "Mounting EBS volume into data directory"
Expand All @@ -33,25 +31,10 @@ resource "aem_instance" "single" {
mkdir -p "${local.aem_single_compose_dir}/aem/home/lib" && \
aws s3 cp --recursive --no-progress "s3://aemc/instance/classic/" "${local.aem_single_compose_dir}/aem/home/lib"
)
EOF
initialize = <<EOF
#!/bin/sh
# sh aemw instance backup restore
EOF
provision = <<EOF
#!/bin/sh
sh aemw osgi bundle install --url "https://github.com/neva-dev/felix-search-webconsole-plugin/releases/download/2.0.0/search-webconsole-plugin-2.0.0.jar" && \
sh aemw osgi config save --pid "org.apache.sling.jcr.davex.impl.servlets.SlingDavExServlet" --input-string "alias: /crx/server" && \
echo "
enabled: true
transportUri: http://localhost:4503/bin/receive?sling:authRequestLogin=1
transportUser: admin
transportPassword: admin
userId: admin
" | sh aemw repl agent setup -A --location "author" --name "publish" && \
sh aemw package deploy --file "aem/home/lib/aem-service-pkg-6.5.*.0.zip"
EOF
SHELL
}

compose {} // TODO must be at least empty; TF plugin framework bug?
}

locals {
Expand Down
122 changes: 71 additions & 51 deletions internal/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"github.com/melbahja/goph"
"github.com/wttech/terraform-provider-aem/internal/utils"
"os"
"path/filepath"
"strings"
Expand All @@ -15,8 +16,9 @@ type Client struct {
settings map[string]string
connection Connection

Env map[string]string
EnvDir string // TODO this is more like tmp script dir
Env map[string]string
WorkDir string
Sudo bool
}

func (c Client) TypeName() string {
Expand Down Expand Up @@ -66,65 +68,46 @@ func (c Client) Connection() Connection {
return c.connection
}

func (c Client) Run(cmdLine []string) (*goph.Cmd, error) {
func (c Client) Command(cmdLine []string) (*goph.Cmd, error) {
return c.connection.Command(cmdLine)
}

func (c Client) SetupEnv() error {
file, err := os.CreateTemp(os.TempDir(), "tf-provider-aem-env-*.sh")
path := file.Name()
defer func() { _ = file.Close(); _ = os.Remove(path) }()
if err != nil {
return fmt.Errorf("cannot create temporary file for remote shell environment script: %w", err)
}
if _, err := file.WriteString(c.envScriptString()); err != nil {
return fmt.Errorf("cannot write temporary file for remote shell environment script: %w", err)
}
if err := c.FileCopy(path, c.envScriptPath(), true); err != nil {
return err
if err := c.FileWrite(c.envScriptPath(), c.envScriptString()); err != nil {
return fmt.Errorf("cannot setup environment script: %w", err)
}
return nil
}

func (c Client) envScriptPath() string {
return fmt.Sprintf("%s/env.sh", c.EnvDir)
return fmt.Sprintf("%s/env.sh", c.WorkDir)
}

func (c Client) envScriptString() string {
var sb strings.Builder
sb.WriteString("#!/bin/sh\n")
for name, value := range c.Env {
escapedValue := strings.ReplaceAll(value, "\"", "\\\"")
escapedValue = strings.ReplaceAll(escapedValue, "$", "\\$")
sb.WriteString(fmt.Sprintf("export %s=\"%s\"\n", name, escapedValue))
}
return sb.String()
return utils.EnvToScript(c.Env)
}

func (c Client) RunShellWithEnv(cmd string) ([]byte, error) {
return c.RunShell(fmt.Sprintf("source %s && %s", c.envScriptPath(), cmd))
func (c Client) RunShellCommand(cmd string) ([]byte, error) {
return c.RunShellPurely(fmt.Sprintf("source %s && %s", c.envScriptPath(), cmd))
}

func (c Client) RunShellScriptWithEnv(dir string, cmdScript string) ([]byte, error) {
file, err := os.CreateTemp(os.TempDir(), "tf-provider-aem-script-*.sh")
path := file.Name()
defer func() { _ = file.Close(); _ = os.Remove(path) }()
if err != nil {
return nil, fmt.Errorf("cannot create temporary file for remote shell script: %w", err)
func (c Client) RunShellScript(cmdName string, cmdScript string, dir string) ([]byte, error) {
remotePath := fmt.Sprintf("%s/%s.sh", c.WorkDir, cmdName)
if err := c.FileWrite(remotePath, cmdScript); err != nil {
return nil, fmt.Errorf("cannot write temporary script at remote path '%s': %w", remotePath, err)
}
if _, err := file.WriteString(cmdScript); err != nil {
return nil, fmt.Errorf("cannot write temporary file for remote shell script: %w", err)
}
remotePath := fmt.Sprintf("%s/%s", c.EnvDir, filepath.Base(file.Name()))
defer func() { _ = c.FileDelete(remotePath) }()
if err := c.FileCopy(path, remotePath, true); err != nil {
return nil, err
}
return c.RunShellWithEnv(fmt.Sprintf("cd %s && sh %s", dir, remotePath))
return c.RunShellCommand(fmt.Sprintf("cd %s && sh %s", dir, remotePath))
}

func (c Client) RunShell(cmd string) ([]byte, error) {
cmdObj, err := c.connection.Command([]string{"sh", "-c", "\"" + cmd + "\""})
func (c Client) RunShellPurely(cmd string) ([]byte, error) {
var cmdLine []string
if c.Sudo {
cmdLine = []string{"sudo", "sh", "-c", "\"" + cmd + "\""}
} else {
cmdLine = []string{"sh", "-c", "\"" + cmd + "\""}
}
cmdObj, err := c.connection.Command(cmdLine)
if err != nil {
return nil, fmt.Errorf("cannot create command '%s': %w", cmd, err)
}
Expand All @@ -139,15 +122,15 @@ func (c Client) RunShell(cmd string) ([]byte, error) {
}

func (c Client) DirEnsure(path string) error {
_, err := c.RunShell(fmt.Sprintf("mkdir -p %s", path))
_, err := c.RunShellPurely(fmt.Sprintf("mkdir -p %s", path))
if err != nil {
return fmt.Errorf("cannot ensure directory '%s': %w", path, err)
}
return nil
}

func (c Client) FileExists(path string) (bool, error) {
out, err := c.RunShell(fmt.Sprintf("test -f %s && echo '0' || echo '1'", path))
out, err := c.RunShellPurely(fmt.Sprintf("test -f %s && echo '0' || echo '1'", path))
if err != nil {
return false, fmt.Errorf("cannot check if file exists '%s': %w", path, err)
}
Expand All @@ -158,14 +141,22 @@ func (c Client) FileMove(oldPath string, newPath string) error {
if err := c.DirEnsure(filepath.Dir(newPath)); err != nil {
return err
}
if _, err := c.RunShell(fmt.Sprintf("mv %s %s", oldPath, newPath)); err != nil {
if _, err := c.RunShellPurely(fmt.Sprintf("mv %s %s", oldPath, newPath)); err != nil {
return fmt.Errorf("cannot move file '%s' to '%s': %w", oldPath, newPath, err)
}
return nil
}

func (c Client) FileMakeExecutable(path string) error {
_, err := c.RunShellPurely(fmt.Sprintf("chmod +x %s", path))
if err != nil {
return fmt.Errorf("cannot make file executable '%s': %w", path, err)
}
return nil
}

func (c Client) DirExists(path string) (bool, error) {
out, err := c.RunShell(fmt.Sprintf("test -d %s && echo '0' || echo '1'", path))
out, err := c.RunShellPurely(fmt.Sprintf("test -d %s && echo '0' || echo '1'", path))
if err != nil {
return false, fmt.Errorf("cannot check if directory exists '%s': %w", path, err)
}
Expand Down Expand Up @@ -197,13 +188,12 @@ func (c Client) DirCopy(localPath string, remotePath string, override bool) erro
}

func (c Client) FileDelete(path string) error {
if _, err := c.RunShell(fmt.Sprintf("rm -rf %s", path)); err != nil {
if _, err := c.RunShellPurely(fmt.Sprintf("rm -rf %s", path)); err != nil {
return fmt.Errorf("cannot delete file '%s': %w", path, err)
}
return nil
}

// TODO seems that if file exists it is not skipping copying file
func (c Client) FileCopy(localPath string, remotePath string, override bool) error {
if !override {
exists, err := c.FileExists(remotePath)
Expand All @@ -217,10 +207,13 @@ func (c Client) FileCopy(localPath string, remotePath string, override bool) err
if err := c.DirEnsure(filepath.Dir(remotePath)); err != nil {
return err
}
remoteTmpPath := fmt.Sprintf("%s.tmp", remotePath)
defer func() {
_ = c.FileDelete(remoteTmpPath)
}()
var remoteTmpPath string
if c.Sudo { // assume that work dir is writable without sudo for uploading time
remoteTmpPath = fmt.Sprintf("%s/%s.tmp", c.WorkDir, filepath.Base(remotePath))
} else {
remoteTmpPath = fmt.Sprintf("%s.tmp", remotePath)
}
defer func() { _ = c.FileDelete(remoteTmpPath) }()
if err := c.connection.CopyFile(localPath, remoteTmpPath); err != nil {
return err
}
Expand All @@ -229,3 +222,30 @@ func (c Client) FileCopy(localPath string, remotePath string, override bool) err
}
return nil
}

func (c Client) PathCopy(localPath string, remotePath string, override bool) error {
stat, err := os.Stat(localPath)
if err != nil {
return fmt.Errorf("cannot stat path '%s': %w", localPath, err)
}
if stat.IsDir() {
return c.DirCopy(localPath, remotePath, override)
}
return c.FileCopy(localPath, remotePath, override)
}

func (c Client) FileWrite(remotePath string, text string) error {
file, err := os.CreateTemp(os.TempDir(), "tf-provider-aem-*.tmp")
path := file.Name()
defer func() { _ = file.Close(); _ = os.Remove(path) }()
if err != nil {
return fmt.Errorf("cannot create local writable temporary file to be copied to remote path '%s': %w", remotePath, err)
}
if _, err := file.WriteString(text); err != nil {
return fmt.Errorf("cannot write text to local temporary file to be copied to remote path '%s': %w", remotePath, err)
}
if err := c.FileCopy(path, remotePath, true); err != nil {
return err
}
return nil
}
1 change: 1 addition & 0 deletions internal/client/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

type Connection interface {
Info() string
User() string
Connect() error
Disconnect() error
Command(cmdLine []string) (*goph.Cmd, error)
Expand Down
5 changes: 5 additions & 0 deletions internal/client/connection_aws_ssm.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ func (A AWSSSMConnection) Info() string {
panic("implement me")
}

func (A AWSSSMConnection) User() string {
//TODO implement me
panic("implement me")
}

func (A AWSSSMConnection) Connect() error {
//TODO implement me
panic("implement me")
Expand Down
4 changes: 4 additions & 0 deletions internal/client/connection_ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ func (s *SSHConnection) Info() string {
return fmt.Sprintf("ssh: host='%s', user='%s', port='%d'", s.host, s.user, s.port)
}

func (s *SSHConnection) User() string {
return s.user
}

func (s *SSHConnection) Disconnect() error {
if s.client == nil {
return nil
Expand Down
File renamed without changes.
4 changes: 4 additions & 0 deletions internal/provider/instance/create.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh

sh aemw instance create && \
sh aemw instance init
3 changes: 3 additions & 0 deletions internal/provider/instance/delete.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/sh

sh aemw instance delete
18 changes: 18 additions & 0 deletions internal/provider/instance/embed.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package instance

import _ "embed"

//go:embed config.yml
var ConfigYML string

//go:embed systemd.conf
var ServiceConf string

//go:embed create.sh
var CreateScript string

//go:embed launch.sh
var LaunchScript string

//go:embed delete.sh
var DeleteScript string
10 changes: 10 additions & 0 deletions internal/provider/instance/launch.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/sh

sh aemw osgi config save --pid "org.apache.sling.jcr.davex.impl.servlets.SlingDavExServlet" --input-string "alias: /crx/server" && \
echo "
enabled: true
transportUri: http://localhost:4503/bin/receive?sling:authRequestLogin=1
transportUser: admin
transportPassword: admin
userId: admin
" | sh aemw repl agent setup -A --location "author" --name "publish"
42 changes: 0 additions & 42 deletions internal/provider/instance/plan_modifiers.go

This file was deleted.

Loading

0 comments on commit 0d429cd

Please sign in to comment.