Skip to content

Commit e2fb96f

Browse files
Merge pull request #267 from wttech/vault-cli
Push command, Vault-Cli wrapper, improvements this PR main feature is to introduce working `content push` command. the Vault CLI is also added here but in 'incubating' mode. so it could be removed in the future if there will be no use case found. it is here because it was considered as a transport mode for content pull/push but after all it's not so effective. we decided to keep it in AEMC to leverage auto-install capability that may become useful sometimes if sb will want to use `vlt rcp` command or similar
2 parents 405a4f3 + 47bb19e commit e2fb96f

File tree

80 files changed

+2933
-458
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+2933
-458
lines changed

README.md

+49
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,9 @@ java:
471471
"arm64": "x64"
472472
"aarch64": "x64"
473473

474+
vault:
475+
"download_url": "https://repo1.maven.org/maven2/org/apache/jackrabbit/vault/vault-cli/3.7.2/vault-cli-3.7.2-bin.tar.gz"
476+
474477
base:
475478
# Location of temporary files (downloaded AEM packages, etc)
476479
tmp_dir: aem/home/tmp
@@ -493,6 +496,52 @@ output:
493496
file: aem/home/var/log/aem.log
494497
# Controls where outputs and logs should be written to when format is 'text' (console|file|both)
495498
mode: console
499+
500+
# Content clean options
501+
content:
502+
clean:
503+
# File patterns to be deleted
504+
files_deleted:
505+
- patterns:
506+
- "**/.vlt"
507+
- "**/.vlt*.tmp"
508+
- "**/install/*.jar"
509+
# File patterns to be flattened
510+
files_flattened:
511+
- "**/_cq_design_dialog/.content.xml"
512+
- "**/_cq_dialog/.content.xml"
513+
- "**/_cq_htmlTag/.content.xml"
514+
- "**/_cq_template/.content.xml"
515+
# Property patterns to be skipped, removed from cleaned file
516+
properties_skipped:
517+
- patterns: "jcr:uuid"
518+
excluded_paths: [ "**/home/users/*", "**/home/groups/*" ]
519+
- patterns: "cq:lastModified*"
520+
excluded_paths: [ "**/content/experience-fragments/*" ]
521+
- patterns: [ "dam:sha1", "dam:size" ]
522+
included_paths: [ "**/content/dam/*.svg/*" ]
523+
- patterns:
524+
- "jcr:lastModified*"
525+
- "jcr:created*"
526+
- "jcr:isCheckedOut"
527+
- "cq:lastReplicat*"
528+
- "cq:lastRolledout*"
529+
- "dam:extracted"
530+
- "dam:assetState"
531+
- "dc:modified"
532+
- "*_x0040_*"
533+
- "cq:name"
534+
- "cq:parentPath"
535+
- "dam:copiedAt"
536+
- "dam:parentAssetID"
537+
- "dam:relativePath"
538+
# Mixin type patterns to be skipped, removed from cleaned file
539+
mixin_types_skipped:
540+
- patterns:
541+
- "cq:ReplicationStatus"
542+
- "mix:versionable"
543+
# Unused namespaces to be skipped, removed from cleaned file
544+
namespaces_skipped: true
496545
```
497546

498547
Note that environment variables may be injected in any part of config file.

cmd/aem/content.go

+171-36
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import (
55
"github.com/spf13/cobra"
66
"github.com/wttech/aemc/pkg"
77
"github.com/wttech/aemc/pkg/common/pathx"
8+
"github.com/wttech/aemc/pkg/common/timex"
89
"github.com/wttech/aemc/pkg/content"
10+
"os"
11+
"path/filepath"
912
"strings"
1013
)
1114

@@ -17,6 +20,7 @@ func (c *CLI) contentCmd() *cobra.Command {
1720
}
1821
cmd.AddCommand(c.contentCleanCmd())
1922
cmd.AddCommand(c.contentPullCmd())
23+
cmd.AddCommand(c.contentPushCmd())
2024
cmd.AddCommand(c.contentDownloadCmd())
2125
cmd.AddCommand(c.contentCopyCmd())
2226
return cmd
@@ -38,17 +42,17 @@ func (c *CLI) contentCleanCmd() *cobra.Command {
3842
c.Error(err)
3943
return
4044
}
45+
path := dir
46+
if path == "" {
47+
path = file
48+
}
49+
if err = c.aem.ContentManager().Clean(path); err != nil {
50+
c.Error(err)
51+
return
52+
}
4153
if dir != "" {
42-
if err = c.aem.ContentManager().CleanDir(dir); err != nil {
43-
c.Error(err)
44-
return
45-
}
4654
c.SetOutput("dir", dir)
4755
} else if file != "" {
48-
if err = c.aem.ContentManager().CleanFile(file); err != nil {
49-
c.Error(err)
50-
return
51-
}
5256
c.SetOutput("file", file)
5357
}
5458
c.Changed("content cleaned")
@@ -73,10 +77,14 @@ func (c *CLI) contentDownloadCmd() *cobra.Command {
7377
return
7478
}
7579
pid, _ := cmd.Flags().GetString("pid")
80+
if pid == "" {
81+
pid = fmt.Sprintf("aemc:content-download:%s-SNAPSHOT", timex.FileTimestampForNow())
82+
}
7683
targetFile, _ := cmd.Flags().GetString("target-file")
77-
filterRoots, _ := cmd.Flags().GetStringSlice("filter-roots")
84+
filterRoots := determineFilterRoots(cmd)
7885
filterFile, _ := cmd.Flags().GetString("filter-file")
79-
if err = instance.ContentManager().Download(targetFile, pkg.PackageCreateOpts{
86+
clean, _ := cmd.Flags().GetBool("clean")
87+
if err = c.aem.ContentManager().Download(instance, targetFile, clean, pkg.PackageCreateOpts{
8088
PID: pid,
8189
FilterRoots: filterRoots,
8290
FilterFile: filterFile,
@@ -94,6 +102,7 @@ func (c *CLI) contentDownloadCmd() *cobra.Command {
94102
cmd.Flags().StringSliceP("filter-roots", "r", []string{}, "Vault filter root paths")
95103
cmd.Flags().StringP("filter-file", "f", "", "Vault filter file path")
96104
cmd.MarkFlagsOneRequired("filter-roots", "filter-file")
105+
cmd.Flags().Bool("clean", false, "Normalize content after downloading")
97106
return cmd
98107
}
99108

@@ -108,8 +117,6 @@ func (c *CLI) contentPullCmd() *cobra.Command {
108117
c.Error(err)
109118
return
110119
}
111-
clean, _ := cmd.Flags().GetBool("clean")
112-
replace, _ := cmd.Flags().GetBool("replace")
113120
dir, err := determineContentDir(cmd)
114121
if err != nil {
115122
c.Error(err)
@@ -120,21 +127,26 @@ func (c *CLI) contentPullCmd() *cobra.Command {
120127
c.Error(err)
121128
return
122129
}
130+
filterRoots := determineFilterRoots(cmd)
131+
filterFile, _ := cmd.Flags().GetString("filter-file")
132+
filterRootExcludes := determineFilterRootExcludes(cmd)
133+
clean, _ := cmd.Flags().GetBool("clean")
134+
replace, _ := cmd.Flags().GetBool("replace")
123135
if dir != "" {
124-
filterRoots, _ := cmd.Flags().GetStringSlice("filter-roots")
125-
filterFile, _ := cmd.Flags().GetString("filter-file")
126-
if err = instance.ContentManager().PullDir(dir, clean, replace, pkg.PackageCreateOpts{
136+
if err = c.aem.ContentManager().PullDir(instance, dir, clean, replace, pkg.PackageCreateOpts{
137+
PID: fmt.Sprintf("aemc:content-pull:%s-SNAPSHOT", timex.FileTimestampForNow()),
127138
FilterRoots: filterRoots,
128139
FilterFile: filterFile,
129-
ContentDir: dir,
130140
}); err != nil {
131141
c.Error(err)
132142
return
133143
}
134144
c.SetOutput("dir", dir)
135145
} else if file != "" {
136-
if err = instance.ContentManager().PullFile(file, clean, pkg.PackageCreateOpts{
137-
ContentFile: file,
146+
if err = c.aem.ContentManager().PullFile(instance, file, clean, replace, pkg.PackageCreateOpts{
147+
PID: fmt.Sprintf("aemc:content-pull:%s-SNAPSHOT", timex.FileTimestampForNow()),
148+
FilterRoots: filterRoots,
149+
FilterRootExcludes: filterRootExcludes,
138150
}); err != nil {
139151
c.Error(err)
140152
return
@@ -156,6 +168,66 @@ func (c *CLI) contentPullCmd() *cobra.Command {
156168
return cmd
157169
}
158170

171+
func (c *CLI) contentPushCmd() *cobra.Command {
172+
cmd := &cobra.Command{
173+
Use: "push",
174+
Aliases: []string{"ps"},
175+
Short: "Push content from JCR root directory or local file to running instance",
176+
Run: func(cmd *cobra.Command, args []string) {
177+
instances, err := c.aem.InstanceManager().Some()
178+
if err != nil {
179+
c.Error(err)
180+
return
181+
}
182+
dir, err := determineContentDir(cmd)
183+
if err != nil {
184+
c.Error(err)
185+
return
186+
}
187+
file, err := determineContentFile(cmd)
188+
if err != nil {
189+
c.Error(err)
190+
return
191+
}
192+
path := dir
193+
if path == "" {
194+
path = file
195+
}
196+
if !pathx.Exists(path) {
197+
c.Error(fmt.Errorf("cannot push content as it does not exist '%s'", path))
198+
return
199+
}
200+
filterRoots := determineFilterRoots(cmd)
201+
filterRootExcludes := determineFilterRootExcludes(cmd)
202+
clean, _ := cmd.Flags().GetBool("clean")
203+
filterMode := determineFilterMode(cmd)
204+
if err = c.aem.ContentManager().Push(instances, clean, pkg.PackageCreateOpts{
205+
PID: fmt.Sprintf("aemc:content-push:%s-SNAPSHOT", timex.FileTimestampForNow()),
206+
FilterRoots: filterRoots,
207+
FilterRootExcludes: filterRootExcludes,
208+
ContentPath: path,
209+
FilterMode: filterMode,
210+
}); err != nil {
211+
c.Error(err)
212+
return
213+
}
214+
if dir != "" {
215+
c.SetOutput("dir", dir)
216+
} else if file != "" {
217+
c.SetOutput("file", file)
218+
}
219+
c.Changed("content pushed")
220+
},
221+
}
222+
cmd.Flags().StringP("dir", "d", "", "JCR root path")
223+
cmd.Flags().StringP("file", "f", "", "Local file path")
224+
cmd.Flags().StringP("path", "p", "", "JCR root path or local file path")
225+
cmd.MarkFlagsOneRequired("dir", "file", "path")
226+
cmd.Flags().Bool("clean", false, "Normalize content while uploading")
227+
cmd.Flags().Bool("update", false, "Existing content on running instance is updated, new content is added and none is deleted")
228+
return cmd
229+
}
230+
159231
func (c *CLI) contentCopyCmd() *cobra.Command {
160232
cmd := &cobra.Command{
161233
Use: "copy",
@@ -167,15 +239,16 @@ func (c *CLI) contentCopyCmd() *cobra.Command {
167239
c.Error(err)
168240
return
169241
}
170-
targetInstance, err := determineContentTargetInstance(cmd, c.aem.InstanceManager())
242+
targetInstances, err := determineContentTargetInstances(cmd, c.aem.InstanceManager())
171243
if err != nil {
172244
c.Error(err)
173245
return
174246
}
175-
filterRoots, _ := cmd.Flags().GetStringSlice("filter-roots")
247+
filterRoots := determineFilterRoots(cmd)
176248
filterFile, _ := cmd.Flags().GetString("filter-file")
177249
clean, _ := cmd.Flags().GetBool("clean")
178-
if err = instance.ContentManager().Copy(targetInstance, clean, pkg.PackageCreateOpts{
250+
if err = c.aem.ContentManager().Copy(instance, targetInstances, clean, pkg.PackageCreateOpts{
251+
PID: fmt.Sprintf("aemc:content-copy:%s-SNAPSHOT", timex.FileTimestampForNow()),
179252
FilterRoots: filterRoots,
180253
FilterFile: filterFile,
181254
}); err != nil {
@@ -185,8 +258,8 @@ func (c *CLI) contentCopyCmd() *cobra.Command {
185258
c.Changed("content copied")
186259
},
187260
}
188-
cmd.Flags().StringP("instance-target-url", "u", "", "Destination instance URL")
189-
cmd.Flags().StringP("instance-target-id", "i", "", "Destination instance ID")
261+
cmd.Flags().StringSliceP("instance-target-url", "u", []string{}, "Destination instance URL")
262+
cmd.Flags().StringSliceP("instance-target-id", "i", []string{}, "Destination instance ID")
190263
cmd.MarkFlagsOneRequired("instance-target-url", "instance-target-id")
191264
cmd.Flags().StringSliceP("filter-roots", "r", []string{}, "Vault filter root paths")
192265
cmd.Flags().StringP("filter-file", "f", "", "Vault filter file path")
@@ -195,33 +268,41 @@ func (c *CLI) contentCopyCmd() *cobra.Command {
195268
return cmd
196269
}
197270

198-
func determineContentTargetInstance(cmd *cobra.Command, instanceManager *pkg.InstanceManager) (*pkg.Instance, error) {
199-
var instance *pkg.Instance
200-
url, _ := cmd.Flags().GetString("instance-target-url")
201-
if url != "" {
202-
instance, _ = instanceManager.NewByIDAndURL("remote_adhoc_target", url)
271+
func determineContentTargetInstances(cmd *cobra.Command, instanceManager *pkg.InstanceManager) ([]pkg.Instance, error) {
272+
var instances []pkg.Instance
273+
urls, _ := cmd.Flags().GetStringSlice("instance-target-url")
274+
for _, url := range urls {
275+
instance, err := instanceManager.NewByIDAndURL("remote_adhoc_target", url)
276+
if err != nil {
277+
return nil, err
278+
}
279+
instances = append(instances, *instance)
203280
}
204-
id, _ := cmd.Flags().GetString("instance-target-id")
205-
if id != "" {
206-
instance = instanceManager.NewByID(id)
281+
ids, _ := cmd.Flags().GetStringSlice("instance-target-id")
282+
for _, id := range ids {
283+
instance := instanceManager.NewByID(id)
284+
instances = append(instances, *instance)
207285
}
208-
if instance == nil {
286+
if instances == nil {
209287
return nil, fmt.Errorf("missing 'instance-target-url' or 'instance-target-id'")
210288
}
211-
return instance, nil
289+
return instances, nil
212290
}
213291

214292
func determineContentDir(cmd *cobra.Command) (string, error) {
215293
dir, _ := cmd.Flags().GetString("dir")
216294
if dir != "" && !strings.Contains(dir, content.JCRRoot) {
217-
return "", fmt.Errorf("content dir '%s' does not contain '%s'", dir, content.JCRRoot)
295+
return "", fmt.Errorf("content directory '%s' does not contain '%s'", dir, content.JCRRoot)
296+
}
297+
if pathx.IsFile(dir) {
298+
return "", fmt.Errorf("content directory '%s' is not a directory; consider using 'file' parameter", dir)
218299
}
219300
path, _ := cmd.Flags().GetString("path")
220301
if path != "" && !strings.Contains(path, content.JCRRoot) {
221302
return "", fmt.Errorf("content path '%s' does not contain '%s'", path, content.JCRRoot)
222303
}
223304
if path != "" && !pathx.Exists(path) {
224-
return "", fmt.Errorf("content path does not exist: %s", path)
305+
return "", fmt.Errorf("content path '%s' need to exist on file system; consider using 'dir' or 'file' parameter otherwise", path)
225306
}
226307
if path != "" && pathx.IsDir(path) {
227308
return path, nil
@@ -234,15 +315,69 @@ func determineContentFile(cmd *cobra.Command) (string, error) {
234315
if file != "" && !strings.Contains(file, content.JCRRoot) {
235316
return "", fmt.Errorf("content file '%s' does not contain '%s'", file, content.JCRRoot)
236317
}
318+
if pathx.IsDir(file) {
319+
return "", fmt.Errorf("content file '%s' is not a file; consider using 'dir' parameter", file)
320+
}
237321
path, _ := cmd.Flags().GetString("path")
238322
if path != "" && !strings.Contains(path, content.JCRRoot) {
239323
return "", fmt.Errorf("content path '%s' does not contain '%s'", path, content.JCRRoot)
240324
}
241325
if path != "" && !pathx.Exists(path) {
242-
return "", fmt.Errorf("content path does not exist: %s", path)
326+
return "", fmt.Errorf("content path '%s' need to exist on file system; consider using 'dir' or 'file' parameter otherwise", path)
243327
}
244328
if path != "" && pathx.IsFile(path) {
245329
return path, nil
246330
}
247331
return file, nil
248332
}
333+
334+
func determineFilterRoots(cmd *cobra.Command) []string {
335+
filterRoots, _ := cmd.Flags().GetStringSlice("filter-roots")
336+
if len(filterRoots) > 0 {
337+
return filterRoots
338+
}
339+
filterFile, _ := cmd.Flags().GetString("filter-file")
340+
if filterFile != "" {
341+
return nil
342+
}
343+
dir, _ := determineContentDir(cmd)
344+
if dir != "" {
345+
return []string{pkg.DetermineFilterRoot(dir)}
346+
}
347+
file, _ := determineContentFile(cmd)
348+
if file != "" {
349+
return []string{pkg.DetermineFilterRoot(file)}
350+
}
351+
return nil
352+
}
353+
354+
func determineFilterRootExcludes(cmd *cobra.Command) []string {
355+
file, _ := determineContentFile(cmd)
356+
if file == "" || !strings.HasSuffix(file, content.JCRContentFile) || content.IsPageContentFile(file) {
357+
return nil
358+
}
359+
360+
dir := filepath.Dir(file)
361+
entries, err := os.ReadDir(dir)
362+
if err != nil {
363+
return nil
364+
}
365+
366+
var filterRootExcludes []string
367+
for _, entry := range entries {
368+
if entry.Name() != content.JCRContentFile {
369+
jcrPath := pkg.DetermineFilterRoot(filepath.Join(dir, entry.Name()))
370+
excludePattern := fmt.Sprintf("%s(/.*)?", jcrPath)
371+
filterRootExcludes = append(filterRootExcludes, excludePattern)
372+
}
373+
}
374+
return filterRootExcludes
375+
}
376+
377+
func determineFilterMode(cmd *cobra.Command) string {
378+
update, _ := cmd.Flags().GetBool("update")
379+
if update {
380+
return "update"
381+
}
382+
return ""
383+
}

0 commit comments

Comments
 (0)