Skip to content

Commit

Permalink
Hooks impl
Browse files Browse the repository at this point in the history
  • Loading branch information
krystian-panek-vmltech committed Nov 6, 2023
1 parent 21b0d0b commit 7c4d6ae
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 24 deletions.
21 changes: 21 additions & 0 deletions examples/ssh/aem.tf
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,27 @@ resource "aem_instance" "single" {
lib_dir = "aem/home/lib"
config_file = "aem/default/etc/aem.yml"
}
hook {
bootstrap = <<EOF
#!/bin/sh
sudo yum install -y unzip && \
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \
unzip awscliv2.zip && \
sudo ./aws/install && \
mkdir -p "/home/ec2-user/aemc/aem/home/lib" && \
aws s3 cp --recursive --no-progress "s3://aemc/instance/classic/" "/home/ec2-user/aemc/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" && \
sh aemw package deploy --file "aem/home/lib/aem-service-pkg-6.5.*.0.zip"
EOF
}
}

output "aem_instances" {
Expand Down
20 changes: 0 additions & 20 deletions examples/ssh/aws.tf
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,6 @@ resource "aws_instance" "aem_single" {
iam_instance_profile = aws_iam_instance_profile.aem_ec2.name
key_name = aws_key_pair.main.key_name
tags = local.tags

// TODO? cloud-init status --wait
// TODO if it is in cloud-init then after logging in via SSH this is done async
user_data = <<-EOF
#!/bin/sh
echo "Installing prerequisites"
yum install -y unzip
echo "Installed prerequisites"
echo "Installing AWS CLI"
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
./aws/install
echo "Installed AWS CLI"
echo "Downloading AEM library files"
aws s3 cp --recursive "s3://aemc/instance/classic/" "/home/ec2-user/aemc/aem/home/lib"
echo "Downloaded AEM library files"
EOF
}

data "tls_public_key" "main" {
Expand Down
20 changes: 19 additions & 1 deletion internal/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type Client struct {
connection Connection

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

func (c Client) TypeName() string {
Expand Down Expand Up @@ -105,6 +105,24 @@ func (c Client) RunShellWithEnv(cmd string) ([]byte, error) {
return c.RunShell(fmt.Sprintf("source %s && %s", c.envScriptPath(), cmd))
}

func (c Client) RunShellScriptWithEnv(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)
}
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("sh %s", remotePath))
}

func (c Client) RunShell(cmd string) ([]byte, error) {
cmdObj, err := c.connection.Command([]string{"sh", "-c", "\"" + cmd + "\""})
if err != nil {
Expand Down
31 changes: 31 additions & 0 deletions internal/provider/instance_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,34 @@ func (ic *InstanceClient) ReadStatus() (InstanceStatus, error) {
}
return status, nil
}

func (ic *InstanceClient) bootstrap() error {
return ic.runHook("bootstrap", ic.data.Hook.Bootstrap.ValueString())
}

func (ic *InstanceClient) initialize() error {
return ic.runHook("initialize", ic.data.Hook.Initialize.ValueString())
}

func (ic *InstanceClient) provision() error {
return ic.runHook("provision", ic.data.Hook.Provision.ValueString())
}

func (ic *InstanceClient) runHook(name string, cmdScript string) error {
if cmdScript == "" {
return nil
}

tflog.Info(ic.ctx, fmt.Sprintf("Hook '%s' started", name))

textOut, err := ic.cl.RunShellScriptWithEnv(cmdScript)
if err != nil {
return fmt.Errorf("unable to run hook '%s' properly: %w", name, err)
}
textStr := string(textOut) // TODO how about streaming it line by line to tflog ;)

tflog.Info(ic.ctx, fmt.Sprintf("Hook '%s' finished", name))
tflog.Info(ic.ctx, textStr)

return nil
}
46 changes: 43 additions & 3 deletions internal/provider/instance_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ type InstanceResourceModel struct {
LibDir types.String `tfsdk:"lib_dir"`
InstanceId types.String `tfsdk:"instance_id"`
} `tfsdk:"compose"`
Hook struct {
Bootstrap types.String `tfsdk:"bootstrap"`
Initialize types.String `tfsdk:"initialize"`
Provision types.String `tfsdk:"provision"`
} `tfsdk:"hook"`
Instances types.List `tfsdk:"instances"`
}

Expand Down Expand Up @@ -122,6 +127,25 @@ func (r *InstanceResource) Schema(ctx context.Context, req resource.SchemaReques
},
},
},
"hook": schema.SingleNestedBlock{
MarkdownDescription: "Scripts executed on the remote AEM machine at key stages of the AEM instance lifecycle",
Attributes: map[string]schema.Attribute{
"bootstrap": schema.StringAttribute{
MarkdownDescription: "Executed once after connecting to the instance. Forces instance recreation if changed. Typically used for: providing AEM library files (quickstart.jar, license.properties, service packs), mounting data volume, etc.",
Optional: true,
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
},
"initialize": schema.StringAttribute{
MarkdownDescription: "Executed once after initializing AEM Compose but before launching the instance. Forces instance recreation if changed. Can be used for restoring instances from backup.",
Optional: true,
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
},
"provision": schema.StringAttribute{
MarkdownDescription: "Executed when the instance is launched. Must be idempotent as it is executed always when changed. Typically used for setting up replication agents, installing service packs, etc.",
Optional: true,
},
},
},
},

Attributes: map[string]schema.Attribute{
Expand Down Expand Up @@ -187,14 +211,14 @@ func (r *InstanceResource) Configure(ctx context.Context, req resource.Configure
}

func (r *InstanceResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
r.createOrUpdate(ctx, &req.Plan, &resp.Diagnostics, &resp.State)
r.createOrUpdate(ctx, &req.Plan, &resp.Diagnostics, &resp.State, true)
}

func (r *InstanceResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
r.createOrUpdate(ctx, &req.Plan, &resp.Diagnostics, &resp.State)
r.createOrUpdate(ctx, &req.Plan, &resp.Diagnostics, &resp.State, false)
}

func (r *InstanceResource) createOrUpdate(ctx context.Context, plan *tfsdk.Plan, diags *diag.Diagnostics, state *tfsdk.State) {
func (r *InstanceResource) createOrUpdate(ctx context.Context, plan *tfsdk.Plan, diags *diag.Diagnostics, state *tfsdk.State, create bool) {
model := r.newModel()

// Read Terraform plan data into the model
Expand All @@ -217,6 +241,12 @@ func (r *InstanceResource) createOrUpdate(ctx context.Context, plan *tfsdk.Plan,
}
}(ic)

if create {
if err := ic.bootstrap(); err != nil {
diags.AddError("Unable to bootstrap AEM machine", fmt.Sprintf("%s", err))
return
}
}
if err := ic.prepareDataDir(); err != nil {
diags.AddError("Unable to prepare AEM data directory", fmt.Sprintf("%s", err))
return
Expand All @@ -233,6 +263,12 @@ func (r *InstanceResource) createOrUpdate(ctx context.Context, plan *tfsdk.Plan,
diags.AddError("Unable to copy AEM library dir", fmt.Sprintf("%s", err))
return
}
if create {
if err := ic.initialize(); err != nil {
diags.AddError("Unable to initialize AEM instance", fmt.Sprintf("%s", err))
return
}
}
if err := ic.create(); err != nil {
diags.AddError("Unable to create AEM instance", fmt.Sprintf("%s", err))
return
Expand All @@ -241,6 +277,10 @@ func (r *InstanceResource) createOrUpdate(ctx context.Context, plan *tfsdk.Plan,
diags.AddError("Unable to launch AEM instance", fmt.Sprintf("%s", err))
return
}
if err := ic.provision(); err != nil {
diags.AddError("Unable to provision AEM instance", fmt.Sprintf("%s", err))
return
}

tflog.Info(ctx, "Finished setting up AEM instance resource")

Expand Down

0 comments on commit 7c4d6ae

Please sign in to comment.