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
223
224
|
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2016, Ryan Scott Brown <ryansb@redhat.com>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
---
module: serverless
short_description: Manages a Serverless Framework project
description:
- Provides support for managing Serverless Framework (U(https://serverless.com/)) project deployments and stacks.
extends_documentation_fragment:
- community.general.attributes
attributes:
check_mode:
support: none
diff_mode:
support: none
options:
state:
description:
- Goal state of given stage/project.
type: str
choices: [ absent, present ]
default: present
serverless_bin_path:
description:
- The path of a serverless framework binary relative to the 'service_path' eg. node_module/.bin/serverless
type: path
service_path:
description:
- The path to the root of the Serverless Service to be operated on.
type: path
required: true
stage:
description:
- The name of the serverless framework project stage to deploy to.
- This uses the serverless framework default "dev".
type: str
default: ''
region:
description:
- AWS region to deploy the service to.
- This parameter defaults to C(us-east-1).
type: str
default: ''
deploy:
description:
- Whether or not to deploy artifacts after building them.
- When this option is C(false) all the functions will be built, but no stack update will be run to send them out.
- This is mostly useful for generating artifacts to be stored/deployed elsewhere.
type: bool
default: true
force:
description:
- Whether or not to force full deployment, equivalent to serverless C(--force) option.
type: bool
default: false
verbose:
description:
- Shows all stack events during deployment, and display any Stack Output.
type: bool
default: false
notes:
- Currently, the C(serverless) command must be in the path of the node executing the task.
In the future this may be a flag.
requirements:
- serverless
- yaml
author:
- Ryan Scott Brown (@ryansb)
'''
EXAMPLES = r'''
- name: Basic deploy of a service
community.general.serverless:
service_path: '{{ project_dir }}'
state: present
- name: Deploy a project, then pull its resource list back into Ansible
community.general.serverless:
stage: dev
region: us-east-1
service_path: '{{ project_dir }}'
register: sls
# The cloudformation stack is always named the same as the full service, so the
# cloudformation_info module can get a full list of the stack resources, as
# well as stack events and outputs
- cloudformation_info:
region: us-east-1
stack_name: '{{ sls.service_name }}'
stack_resources: true
- name: Deploy a project using a locally installed serverless binary
community.general.serverless:
stage: dev
region: us-east-1
service_path: '{{ project_dir }}'
serverless_bin_path: node_modules/.bin/serverless
'''
RETURN = r'''
service_name:
type: str
description: The service name specified in the serverless.yml that was just deployed.
returned: always
sample: my-fancy-service-dev
state:
type: str
description: Whether the stack for the serverless project is present/absent.
returned: always
command:
type: str
description: Full C(serverless) command run by this module, in case you want to re-run the command outside the module.
returned: always
sample: serverless deploy --stage production
'''
import os
try:
import yaml
HAS_YAML = True
except ImportError:
HAS_YAML = False
from ansible.module_utils.basic import AnsibleModule
def read_serverless_config(module):
path = module.params.get('service_path')
full_path = os.path.join(path, 'serverless.yml')
try:
with open(full_path) as sls_config:
config = yaml.safe_load(sls_config.read())
return config
except IOError as e:
module.fail_json(msg="Could not open serverless.yml in {0}. err: {1}".format(full_path, str(e)))
def get_service_name(module, stage):
config = read_serverless_config(module)
if config.get('service') is None:
module.fail_json(msg="Could not read `service` key from serverless.yml file")
if stage:
return "{0}-{1}".format(config['service'], stage)
return "{0}-{1}".format(config['service'], config.get('stage', 'dev'))
def main():
module = AnsibleModule(
argument_spec=dict(
service_path=dict(type='path', required=True),
state=dict(type='str', default='present', choices=['absent', 'present']),
region=dict(type='str', default=''),
stage=dict(type='str', default=''),
deploy=dict(type='bool', default=True),
serverless_bin_path=dict(type='path'),
force=dict(type='bool', default=False),
verbose=dict(type='bool', default=False),
),
)
if not HAS_YAML:
module.fail_json(msg='yaml is required for this module')
service_path = module.params.get('service_path')
state = module.params.get('state')
region = module.params.get('region')
stage = module.params.get('stage')
deploy = module.params.get('deploy', True)
force = module.params.get('force', False)
verbose = module.params.get('verbose', False)
serverless_bin_path = module.params.get('serverless_bin_path')
if serverless_bin_path is not None:
command = serverless_bin_path + " "
else:
command = module.get_bin_path("serverless") + " "
if state == 'present':
command += 'deploy '
elif state == 'absent':
command += 'remove '
else:
module.fail_json(msg="State must either be 'present' or 'absent'. Received: {0}".format(state))
if state == 'present':
if not deploy:
command += '--noDeploy '
elif force:
command += '--force '
if region:
command += '--region {0} '.format(region)
if stage:
command += '--stage {0} '.format(stage)
if verbose:
command += '--verbose '
rc, out, err = module.run_command(command, cwd=service_path)
if rc != 0:
if state == 'absent' and "-{0}' does not exist".format(stage) in out:
module.exit_json(changed=False, state='absent', command=command,
out=out, service_name=get_service_name(module, stage))
module.fail_json(msg="Failure when executing Serverless command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(rc, out, err))
# gather some facts about the deployment
module.exit_json(changed=True, state='present', out=out, command=command,
service_name=get_service_name(module, stage))
if __name__ == '__main__':
main()
|