File: ls3.lua

package info (click to toggle)
lsyncd 2.2.3-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, buster, sid, trixie
  • size: 484 kB
  • sloc: ansic: 2,640; sh: 81; makefile: 11
file content (222 lines) | stat: -rw-r--r-- 6,287 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
----
-- Example lsyncd configuration for syncing with an Amazon S3 bucket
--
-- This requires the official AWS CLI to be available, and that credentials
-- bet set up through some external method, such as environment variables,
-- IAM profiles or the AWS SDK configuration.
--
-- The AWS CLI sync exclude rules are not as powerful as the ones supported by
-- lsyncd. Hence, some of the do not translate perfectly. For example, '*'
-- (asterisk) matches slashes, while it does not in lsyncd. Hence it is a good
-- idea to only use exclude patterns for full directories, either by using a
-- trailing / (slash) or ** (double asterisk), as those will be correctly
-- translated.
--
-- An initialSync options is provided as a convenience, since it's not easy to
-- make sure exclusion rules match when doing it manually. It will *pull* from
-- the target bucket to the local dir (the opposite of the regular behavior)
-- then exit immediately.
--
-- Author: Daniel Miranda <danielkza2@gmail.com>
--
s3 = {}

s3.checkgauge = {
    onCreate  = false,
    onModify  = false,
    onDelete  = false,
    onStartup = false,
    onMove    = false,

    delete      = true,
    exclude     = true,
    excludeFrom = true,
    target      = true,

    s3 = {
        -- Path to the AWS CLI binary
        awscliBinary  = true,
        -- Extra options to pass to the AWS CLI (as a list)
        awscliOptions = true,
        -- Whether to do a dry-run, and not make any real changes
        dryrun        = true,
        -- Do an initial pull from the bucket and exit immediately.
        initialSync   = true
    }
}

-- Generate a list of exclude flags for the AWS CLI  based on the lsyncd
-- patterns provided. Cache it to avoid re-generating it every time.

local s3Excludes = function(config, excludes)
    if config.s3._excludes == nil then
        config.s3._excludes = {}
        for _, pat in ipairs(excludes) do
            pat = pat:gsub('%*%*', '[[ANY]]')
            pat = pat:gsub('%?',   '[[ANY_BUT_SLASH_ONCE]]')
            pat = pat:gsub('/$',   '/*')
            pat = pat:gsub('%[%[ANY%]%]',                '*')
            pat = pat:gsub('%[%[ANY_BUT_SLASH_ONCE%]%]', '[^/]')

            if pat:match('^/') then
                pat = pat:sub(2, -1)
            else
                pat = '*/' .. pat
            end

            table.insert(config.s3._excludes, '--exclude')
            table.insert(config.s3._excludes, pat)
        end

        log('s3Excludes', table.concat(config.s3._excludes, '\n'))
    end

    return config.s3._excludes
end

-- Generates a command line to call the AWS CLI as configured, with the provided
-- S3 action (such as cp, mv, rm or sync).
-- Returns a tuple of (binaryPath, arguments)
local awscliCommand = function(verb, config)
    local bin = config.s3.awscliBinary
    local args = {'s3', verb, '--only-show-errors'}
    if config.s3.dryrun then
        table.insert(args, '--dryrun')
    end

    if verb == 'sync'
       and (config.delete == true or config.delete == 'startup')
    then
        table.insert(args, '--delete')
    end

    for _, opt in ipairs(config.s3.awscliOptions) do
        table.insert(args, opt)
    end

    return bin, args
end

s3.action = function(inlet)
    local event, event2 = inlet.getEvent()
    -- S3 never actually deals with directories - they are just an illusion
    -- created based on the common prefixes of objects. Hence discard any events
    -- that do not concern files.
    if event.isdir then
        inlet.discardEvent(event)
        return
    end

    local config = inlet.getConfig()
    if event.etype == 'Create' or event.etype == 'Modify' then
        local bin, args = awscliCommand('cp', config)
        spawn(
            event,
            bin,
            args,
            event.sourcePath,
            event.targetPath
        )
    elseif event.etype == 'Delete' then
        if config.delete ~= true and config.delete ~= 'running' then
            inlet.discardEvent(event)
            return
        end

        local bin, args = awscliCommand('rm', config)
        spawn(
            event,
            bin,
            args,
            event.targetPath
        )
    elseif event.etype == 'Move' then
        local bin, args = awscliCommand('mv', config)
        spawn(
            event,
            bin,
            args,
            event.targetPath,
            event2.targetPath
        )
    else
        log('Warn', 'ignored an event of type "', event.etype, '"')
        inlet.discardEvent(event)
    end
end

s3.init = function(event)
    local config = event.config
    local inlet = event.inlet
    local excludes = s3Excludes(config, inlet.getExcludes())
    local bin, args = awscliCommand('sync', config)

    -- Do a pull when initialSync is enabled.
    if config.s3.initialSync then
        spawn(
            event,
            bin,
            args,
            excludes,
            config.target,
            event.sourcePath
        )
    -- And a push, as usual, otherwise
    else
        spawn(
            event,
            bin,
            args,
            excludes,
            event.sourcePath,
            config.target
        )
    end
end

-- Define a collect callback so we can terminate immediately when initialSync
-- is enabled
s3.collect = function(agent, exitcode)
    local config = agent.config
    if not agent.isList and agent.etype == 'Init' and config.s3.initialSync then
        terminate(exitcode == 0 and 0 or -1)
    end

    return
end

s3.prepare = function(config, level)
    default.prepare(config, level + 1)

    config.target = config.target:gsub('/+$', '')
    if not config.target:match('^s3://') then
        config.target = 's3://' .. config.target
    end
end

s3.s3 = {
    awscliBinary  = '/usr/bin/aws',
    awscliOptions = {},
    dryrun       = false,
    initialSync  = false
}
s3.delete       = false
s3.delay        = 10
s3.maxProcesses = 1

sync {
    s3,
    source       = '/my/dir',
    target       = 's3://my-bucket/my-path',
    delay        = 30,
    delete       = true,
    maxProcesses = 2,
    exclude = {
        '/sub/folder/',
    },
    s3 = {
        awscliBinary  = '/usr/local/bin/aws',
        awscliOptions = {'--acl', 'public-read'},
        dryrun        = false
    }
}