1"""
2    :codeauthor: Nicole Thomas <nicole@saltstack.com>
3"""
4
5
6import os
7
8import salt.utils.cloud
9import salt.utils.files
10import salt.utils.yaml
11import yaml
12from tests.integration.cloud.helpers.cloud_test_base import CloudTest
13from tests.support import win_installer
14from tests.support.runtests import RUNTIME_VARS
15from tests.support.unit import skipIf
16
17HAS_WINRM = salt.utils.cloud.HAS_WINRM and salt.utils.cloud.HAS_SMB
18# THis test needs a longer timeout than other cloud tests
19TIMEOUT = 1200
20
21
22class EC2Test(CloudTest):
23    """
24    Integration tests for the EC2 cloud provider in Salt-Cloud
25    """
26
27    PROVIDER = "ec2"
28    REQUIRED_PROVIDER_CONFIG_ITEMS = ("id", "key", "keyname", "private_key", "location")
29
30    @staticmethod
31    def __fetch_installer():
32        # Determine the downloaded installer name by searching the files
33        # directory for the first file that looks like an installer.
34        for path, dirs, files in os.walk(RUNTIME_VARS.FILES):
35            for file in files:
36                if file.startswith(win_installer.PREFIX):
37                    return file
38
39        # If the installer wasn't found in the previous steps, download the latest Windows installer executable
40        name = win_installer.latest_installer_name()
41        path = os.path.join(RUNTIME_VARS.FILES, name)
42        with salt.utils.files.fopen(path, "wb") as fp:
43            win_installer.download_and_verify(fp, name)
44        return name
45
46    @property
47    def installer(self):
48        """
49        Make sure the testing environment has a Windows installer executable.
50        """
51        if not hasattr(self, "_installer"):
52            self._installer = self.__fetch_installer()
53        return self._installer
54
55    def setUp(self):
56        """
57        Sets up the test requirements
58        """
59        group_or_subnet = self.provider_config.get("securitygroup")
60        if not group_or_subnet:
61            group_or_subnet = self.provider_config.get("subnetid")
62
63        if not group_or_subnet:
64            self.skipTest(
65                "securitygroup or subnetid missing for {} config".format(self.PROVIDER)
66            )
67
68        super().setUp()
69
70    def override_profile_config(self, name, data):
71        conf_path = os.path.join(
72            RUNTIME_VARS.TMP_CONF_DIR, "cloud.profiles.d", "ec2.conf"
73        )
74        with salt.utils.files.fopen(conf_path, "r") as fp:
75            conf = yaml.safe_load(fp)
76        conf[name].update(data)
77        with salt.utils.files.fopen(conf_path, "w") as fp:
78            salt.utils.yaml.safe_dump(conf, fp)
79
80    def copy_file(self, name):
81        """
82        Copy a file from tests/integration/files to a test's temporary
83        configuration directory. The path to the file which is created will be
84        returned.
85        """
86        src = os.path.join(RUNTIME_VARS.FILES, name)
87        dst = os.path.join(RUNTIME_VARS.TMP_CONF_DIR, name)
88        with salt.utils.files.fopen(src, "rb") as sfp:
89            with salt.utils.files.fopen(dst, "wb") as dfp:
90                dfp.write(sfp.read())
91        return dst
92
93    def _test_instance(self, profile, debug):
94        """
95        Tests creating and deleting an instance on EC2 (classic)
96        """
97
98        # create the instance
99        cmd = ["-p", profile]
100        if debug:
101            cmd.extend(["-l", "debug"])
102        cmd.append(self.instance_name)
103        ret_val = self.run_cloud(" ".join(cmd), timeout=TIMEOUT)
104
105        # check if instance returned with salt installed
106        self.assertInstanceExists(ret_val)
107        ipv6Address_present = False
108        for each in ret_val:
109            if "ipv6Address:" in each:
110                ipv6Address_present = True
111        assert ipv6Address_present
112
113        self.assertDestroyInstance()
114
115    def test_instance_rename(self):
116        """
117        Tests creating and renaming an instance on EC2 (classic)
118        """
119        # create the instance
120        ret_val = self.run_cloud(
121            "-p ec2-test {} --no-deploy".format(self.instance_name), timeout=TIMEOUT
122        )
123        # check if instance returned
124        self.assertInstanceExists(ret_val)
125
126        changed_name = self.instance_name + "-changed"
127
128        rename_result = self.run_cloud(
129            "-a rename {} newname={} --assume-yes".format(
130                self.instance_name, changed_name
131            ),
132            timeout=TIMEOUT,
133        )
134        self.assertFalse(
135            self._instance_exists(),
136            "Instance wasn't renamed: |\n{}".format(rename_result),
137        )
138        self.assertInstanceExists(instance_name=changed_name)
139
140        self.assertDestroyInstance(changed_name)
141
142    def test_instance(self):
143        """
144        Tests creating and deleting an instance on EC2 (classic)
145        """
146        self._test_instance("ec2-test", debug=False)
147
148    def test_win2012r2_psexec(self):
149        """
150        Tests creating and deleting a Windows 2012r2instance on EC2 using
151        psexec (classic)
152        """
153        # TODO: psexec calls hang and the test fails by timing out. The same
154        # same calls succeed when run outside of the test environment.
155        # FIXME? Does this override need to be undone at the end of the test?
156        self.override_profile_config(
157            "ec2-win2012r2-test",
158            {
159                "use_winrm": False,
160                "userdata_file": self.copy_file("windows-firewall-winexe.ps1"),
161                "win_installer": self.copy_file(self.installer),
162            },
163        )
164        self._test_instance("ec2-win2012r2-test", debug=True)
165
166    @skipIf(not HAS_WINRM, "Skip when winrm dependencies are missing")
167    def test_win2012r2_winrm(self):
168        """
169        Tests creating and deleting a Windows 2012r2 instance on EC2 using
170        winrm (classic)
171        """
172        self.override_profile_config(
173            "ec2-win2012r2-test",
174            {
175                "userdata_file": self.copy_file("windows-firewall.ps1"),
176                "win_installer": self.copy_file(self.installer),
177                "winrm_ssl_verify": False,
178                "use_winrm": True,
179            },
180        )
181        self._test_instance("ec2-win2012r2-test", debug=True)
182
183    def test_win2016_psexec(self):
184        """
185        Tests creating and deleting a Windows 2016 instance on EC2 using winrm
186        (classic)
187        """
188        # TODO: winexe calls hang and the test fails by timing out. The
189        # same calls succeed when run outside of the test environment.
190        self.override_profile_config(
191            "ec2-win2016-test",
192            {
193                "use_winrm": False,
194                "userdata_file": self.copy_file("windows-firewall-winexe.ps1"),
195                "win_installer": self.copy_file(self.installer),
196            },
197        )
198        self._test_instance("ec2-win2016-test", debug=True)
199
200    @skipIf(not HAS_WINRM, "Skip when winrm dependencies are missing")
201    def test_win2016_winrm(self):
202        """
203        Tests creating and deleting a Windows 2016 instance on EC2 using winrm
204        (classic)
205        """
206        self.override_profile_config(
207            "ec2-win2016-test",
208            {
209                "userdata_file": self.copy_file("windows-firewall.ps1"),
210                "win_installer": self.copy_file(self.installer),
211                "winrm_ssl_verify": False,
212                "use_winrm": True,
213            },
214        )
215        self._test_instance("ec2-win2016-test", debug=True)
216