1#!/usr/local/bin/python3.8
2#
3# Copyright (c) 2017 Zim Kalinowski, <zikalino@microsoft.com>
4#
5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6
7from __future__ import absolute_import, division, print_function
8__metaclass__ = type
9
10
11DOCUMENTATION = '''
12---
13module: azure_rm_sqlserver
14version_added: "0.1.2"
15short_description: Manage SQL Server instance
16description:
17    - Create, update and delete instance of SQL Server.
18
19options:
20    resource_group:
21        description:
22            - The name of the resource group that contains the resource. You can obtain this value from the Azure Resource Manager API or the portal.
23        required: True
24    name:
25        description:
26            - The name of the server.
27        required: True
28    location:
29        description:
30            - Resource location.
31    admin_username:
32        description:
33            - Administrator username for the server. Once created it cannot be changed.
34    admin_password:
35        description:
36            - The administrator login password (required for server creation).
37    version:
38        description:
39            - The version of the server. For example C(12.0).
40    identity:
41        description:
42            - The identity type. Set this to C(SystemAssigned) in order to automatically create and assign an Azure Active Directory principal for the resource.
43            - Possible values include C(SystemAssigned).
44    state:
45        description:
46            - State of the SQL server. Use C(present) to create or update a server and use C(absent) to delete a server.
47        default: present
48        choices:
49            - absent
50            - present
51
52extends_documentation_fragment:
53    - azure.azcollection.azure
54    - azure.azcollection.azure_tags
55
56author:
57    - Zim Kalinowski (@zikalino)
58
59'''
60
61EXAMPLES = '''
62  - name: Create (or update) SQL Server
63    azure_rm_sqlserver:
64      resource_group: myResourceGroup
65      name: server_name
66      location: westus
67      admin_username: mylogin
68      admin_password: Testpasswordxyz12!
69'''
70
71RETURN = '''
72id:
73    description:
74        - Resource ID.
75    returned: always
76    type: str
77    sample: /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myResourceGroup/providers/Microsoft.Sql/servers/sqlcrudtest-4645
78version:
79    description:
80        - The version of the server.
81    returned: always
82    type: str
83    sample: 12.0
84state:
85    description:
86        - The state of the server.
87    returned: always
88    type: str
89    sample: state
90fully_qualified_domain_name:
91    description:
92        - The fully qualified domain name of the server.
93    returned: always
94    type: str
95    sample: sqlcrudtest-4645.database.windows.net
96'''
97
98import time
99from ansible_collections.azure.azcollection.plugins.module_utils.azure_rm_common import AzureRMModuleBase
100
101try:
102    from msrestazure.azure_exceptions import CloudError
103    from msrest.polling import LROPoller
104    from azure.mgmt.sql import SqlManagementClient
105    from msrest.serialization import Model
106except ImportError:
107    # This is handled in azure_rm_common
108    pass
109
110
111class Actions:
112    NoAction, Create, Update, Delete = range(4)
113
114
115class AzureRMSqlServer(AzureRMModuleBase):
116    """Configuration class for an Azure RM SQL Server resource"""
117
118    def __init__(self):
119        self.module_arg_spec = dict(
120            resource_group=dict(
121                type='str',
122                required=True
123            ),
124            name=dict(
125                type='str',
126                required=True
127            ),
128            location=dict(
129                type='str'
130            ),
131            admin_username=dict(
132                type='str'
133            ),
134            admin_password=dict(
135                type='str',
136                no_log=True
137            ),
138            version=dict(
139                type='str'
140            ),
141            identity=dict(
142                type='str'
143            ),
144            state=dict(
145                type='str',
146                default='present',
147                choices=['present', 'absent']
148            )
149        )
150
151        self.resource_group = None
152        self.name = None
153        self.parameters = dict()
154        self.tags = None
155
156        self.results = dict(changed=False)
157        self.state = None
158        self.to_do = Actions.NoAction
159
160        super(AzureRMSqlServer, self).__init__(derived_arg_spec=self.module_arg_spec,
161                                               supports_check_mode=True,
162                                               supports_tags=True)
163
164    def exec_module(self, **kwargs):
165        """Main module execution method"""
166
167        for key in list(self.module_arg_spec.keys()) + ['tags']:
168            if hasattr(self, key):
169                setattr(self, key, kwargs[key])
170            elif kwargs[key] is not None:
171                if key == "location":
172                    self.parameters.update({"location": kwargs[key]})
173                elif key == "admin_username":
174                    self.parameters.update({"administrator_login": kwargs[key]})
175                elif key == "admin_password":
176                    self.parameters.update({"administrator_login_password": kwargs[key]})
177                elif key == "version":
178                    self.parameters.update({"version": kwargs[key]})
179                elif key == "identity":
180                    self.parameters.update({"identity": {"type": kwargs[key]}})
181
182        old_response = None
183        response = None
184        results = dict()
185
186        resource_group = self.get_resource_group(self.resource_group)
187
188        if "location" not in self.parameters:
189            self.parameters["location"] = resource_group.location
190
191        old_response = self.get_sqlserver()
192
193        if not old_response:
194            self.log("SQL Server instance doesn't exist")
195            if self.state == 'absent':
196                self.log("Old instance didn't exist")
197            else:
198                self.to_do = Actions.Create
199        else:
200            self.log("SQL Server instance already exists")
201            if self.state == 'absent':
202                self.to_do = Actions.Delete
203            elif self.state == 'present':
204                self.log("Need to check if SQL Server instance has to be deleted or may be updated")
205                update_tags, newtags = self.update_tags(old_response.get('tags', dict()))
206                if update_tags:
207                    self.tags = newtags
208                self.to_do = Actions.Update
209
210        if (self.to_do == Actions.Create) or (self.to_do == Actions.Update):
211            self.log("Need to Create / Update the SQL Server instance")
212
213            if self.check_mode:
214                self.results['changed'] = True
215                return self.results
216
217            self.parameters['tags'] = self.tags
218            response = self.create_update_sqlserver()
219            response.pop('administrator_login_password', None)
220
221            if not old_response:
222                self.results['changed'] = True
223            else:
224                self.results['changed'] = old_response.__ne__(response)
225            self.log("Creation / Update done")
226        elif self.to_do == Actions.Delete:
227            self.log("SQL Server instance deleted")
228            self.results['changed'] = True
229
230            if self.check_mode:
231                return self.results
232
233            self.delete_sqlserver()
234            # make sure instance is actually deleted, for some Azure resources, instance is hanging around
235            # for some time after deletion -- this should be really fixed in Azure
236            while self.get_sqlserver():
237                time.sleep(20)
238        else:
239            self.log("SQL Server instance unchanged")
240            self.results['changed'] = False
241            response = old_response
242
243        if response:
244            self.results["id"] = response["id"]
245            self.results["version"] = response["version"]
246            self.results["state"] = response["state"]
247            self.results["fully_qualified_domain_name"] = response["fully_qualified_domain_name"]
248
249        return self.results
250
251    def create_update_sqlserver(self):
252        '''
253        Creates or updates SQL Server with the specified configuration.
254
255        :return: deserialized SQL Server instance state dictionary
256        '''
257        self.log("Creating / Updating the SQL Server instance {0}".format(self.name))
258
259        try:
260            response = self.sql_client.servers.create_or_update(self.resource_group,
261                                                                self.name,
262                                                                self.parameters)
263            if isinstance(response, LROPoller):
264                response = self.get_poller_result(response)
265
266        except CloudError as exc:
267            self.log('Error attempting to create the SQL Server instance.')
268            self.fail("Error creating the SQL Server instance: {0}".format(str(exc)))
269        return response.as_dict()
270
271    def delete_sqlserver(self):
272        '''
273        Deletes specified SQL Server instance in the specified subscription and resource group.
274
275        :return: True
276        '''
277        self.log("Deleting the SQL Server instance {0}".format(self.name))
278        try:
279            response = self.sql_client.servers.delete(self.resource_group,
280                                                      self.name)
281        except CloudError as e:
282            self.log('Error attempting to delete the SQL Server instance.')
283            self.fail("Error deleting the SQL Server instance: {0}".format(str(e)))
284
285        return True
286
287    def get_sqlserver(self):
288        '''
289        Gets the properties of the specified SQL Server.
290
291        :return: deserialized SQL Server instance state dictionary
292        '''
293        self.log("Checking if the SQL Server instance {0} is present".format(self.name))
294        found = False
295        try:
296            response = self.sql_client.servers.get(self.resource_group,
297                                                   self.name)
298            found = True
299            self.log("Response : {0}".format(response))
300            self.log("SQL Server instance : {0} found".format(response.name))
301        except CloudError as e:
302            self.log('Did not find the SQL Server instance.')
303        if found is True:
304            return response.as_dict()
305
306        return False
307
308
309def main():
310    """Main execution"""
311    AzureRMSqlServer()
312
313
314if __name__ == '__main__':
315    main()
316