summaryrefslogtreecommitdiff
path: root/azurelinuxagent/distro
diff options
context:
space:
mode:
Diffstat (limited to 'azurelinuxagent/distro')
-rw-r--r--azurelinuxagent/distro/__init__.py19
-rw-r--r--azurelinuxagent/distro/centos/__init__.py19
-rw-r--r--azurelinuxagent/distro/centos/loader.py25
-rw-r--r--azurelinuxagent/distro/coreos/__init__.py18
-rw-r--r--azurelinuxagent/distro/coreos/deprovision.py30
-rw-r--r--azurelinuxagent/distro/coreos/handlerFactory.py27
-rw-r--r--azurelinuxagent/distro/coreos/loader.py28
-rw-r--r--azurelinuxagent/distro/coreos/osutil.py90
-rw-r--r--azurelinuxagent/distro/debian/__init__.py19
-rw-r--r--azurelinuxagent/distro/debian/loader.py24
-rw-r--r--azurelinuxagent/distro/debian/osutil.py47
-rw-r--r--azurelinuxagent/distro/default/__init__.py19
-rw-r--r--azurelinuxagent/distro/default/deprovision.py117
-rw-r--r--azurelinuxagent/distro/default/dhcp.py330
-rw-r--r--azurelinuxagent/distro/default/env.py115
-rw-r--r--azurelinuxagent/distro/default/extension.py647
-rw-r--r--azurelinuxagent/distro/default/handlerFactory.py40
-rw-r--r--azurelinuxagent/distro/default/init.py49
-rw-r--r--azurelinuxagent/distro/default/loader.py28
-rw-r--r--azurelinuxagent/distro/default/osutil.py657
-rw-r--r--azurelinuxagent/distro/default/provision.py165
-rw-r--r--azurelinuxagent/distro/default/resourceDisk.py166
-rw-r--r--azurelinuxagent/distro/default/run.py86
-rw-r--r--azurelinuxagent/distro/default/scvmm.py47
-rw-r--r--azurelinuxagent/distro/loader.py46
-rw-r--r--azurelinuxagent/distro/oracle/__init__.py19
-rw-r--r--azurelinuxagent/distro/oracle/loader.py25
-rw-r--r--azurelinuxagent/distro/redhat/__init__.py19
-rw-r--r--azurelinuxagent/distro/redhat/loader.py28
-rw-r--r--azurelinuxagent/distro/redhat/osutil.py148
-rw-r--r--azurelinuxagent/distro/suse/__init__.py19
-rw-r--r--azurelinuxagent/distro/suse/loader.py29
-rw-r--r--azurelinuxagent/distro/suse/osutil.py88
-rw-r--r--azurelinuxagent/distro/ubuntu/__init__.py19
-rw-r--r--azurelinuxagent/distro/ubuntu/deprovision.py43
-rw-r--r--azurelinuxagent/distro/ubuntu/handlerFactory.py29
-rw-r--r--azurelinuxagent/distro/ubuntu/loader.py36
-rw-r--r--azurelinuxagent/distro/ubuntu/osutil.py65
-rw-r--r--azurelinuxagent/distro/ubuntu/provision.py72
39 files changed, 3497 insertions, 0 deletions
diff --git a/azurelinuxagent/distro/__init__.py b/azurelinuxagent/distro/__init__.py
new file mode 100644
index 0000000..4b2b9e1
--- /dev/null
+++ b/azurelinuxagent/distro/__init__.py
@@ -0,0 +1,19 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
diff --git a/azurelinuxagent/distro/centos/__init__.py b/azurelinuxagent/distro/centos/__init__.py
new file mode 100644
index 0000000..4b2b9e1
--- /dev/null
+++ b/azurelinuxagent/distro/centos/__init__.py
@@ -0,0 +1,19 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
diff --git a/azurelinuxagent/distro/centos/loader.py b/azurelinuxagent/distro/centos/loader.py
new file mode 100644
index 0000000..379f027
--- /dev/null
+++ b/azurelinuxagent/distro/centos/loader.py
@@ -0,0 +1,25 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+from azurelinuxagent.metadata import DISTRO_NAME, DISTRO_VERSION
+import azurelinuxagent.distro.redhat.loader as redhat
+
+def get_osutil():
+ return redhat.get_osutil()
+
diff --git a/azurelinuxagent/distro/coreos/__init__.py b/azurelinuxagent/distro/coreos/__init__.py
new file mode 100644
index 0000000..7a4980e
--- /dev/null
+++ b/azurelinuxagent/distro/coreos/__init__.py
@@ -0,0 +1,18 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
diff --git a/azurelinuxagent/distro/coreos/deprovision.py b/azurelinuxagent/distro/coreos/deprovision.py
new file mode 100644
index 0000000..f0ff604
--- /dev/null
+++ b/azurelinuxagent/distro/coreos/deprovision.py
@@ -0,0 +1,30 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import azurelinuxagent.utils.fileutil as fileutil
+from azurelinuxagent.distro.default.deprovision import DeprovisionHandler, DeprovisionAction
+
+class CoreOSDeprovisionHandler(DeprovisionHandler):
+ def setup(self, deluser):
+ warnings, actions = super(CoreOSDeprovisionHandler, self).setup(deluser)
+ warnings.append("WARNING! /etc/machine-id will be removed.")
+ files_to_del = ['/etc/machine-id']
+ actions.append(DeprovisionAction(fileutil.rm_files, files_to_del))
+ return warnings, actions
+
diff --git a/azurelinuxagent/distro/coreos/handlerFactory.py b/azurelinuxagent/distro/coreos/handlerFactory.py
new file mode 100644
index 0000000..f0490e8
--- /dev/null
+++ b/azurelinuxagent/distro/coreos/handlerFactory.py
@@ -0,0 +1,27 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+from .deprovision import CoreOSDeprovisionHandler
+from azurelinuxagent.distro.default.handlerFactory import DefaultHandlerFactory
+
+class CoreOSHandlerFactory(DefaultHandlerFactory):
+ def __init__(self):
+ super(CoreOSHandlerFactory, self).__init__()
+ self.deprovision_handler = CoreOSDeprovisionHandler()
+
diff --git a/azurelinuxagent/distro/coreos/loader.py b/azurelinuxagent/distro/coreos/loader.py
new file mode 100644
index 0000000..ec009ef
--- /dev/null
+++ b/azurelinuxagent/distro/coreos/loader.py
@@ -0,0 +1,28 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+
+def get_osutil():
+ from azurelinuxagent.distro.coreos.osutil import CoreOSUtil
+ return CoreOSUtil()
+
+def get_handlers():
+ from azurelinuxagent.distro.coreos.handlerFactory import CoreOSHandlerFactory
+ return CoreOSHandlerFactory()
+
diff --git a/azurelinuxagent/distro/coreos/osutil.py b/azurelinuxagent/distro/coreos/osutil.py
new file mode 100644
index 0000000..6dfba64
--- /dev/null
+++ b/azurelinuxagent/distro/coreos/osutil.py
@@ -0,0 +1,90 @@
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import os
+import re
+import pwd
+import shutil
+import socket
+import array
+import struct
+import fcntl
+import time
+import base64
+import azurelinuxagent.logger as logger
+import azurelinuxagent.utils.fileutil as fileutil
+import azurelinuxagent.utils.shellutil as shellutil
+import azurelinuxagent.utils.textutil as textutil
+from azurelinuxagent.distro.default.osutil import DefaultOSUtil
+
+class CoreOSUtil(DefaultOSUtil):
+ def __init__(self):
+ super(CoreOSUtil, self).__init__()
+ self.waagent_path='/usr/share/oem/bin/waagent'
+ self.python_path='/usr/share/oem/python/bin'
+ self.conf_path = '/usr/share/oem/waagent.conf'
+ if 'PATH' in os.environ:
+ path = "{0}:{1}".format(os.environ['PATH'], self.python_path)
+ else:
+ path = self.python_path
+ os.environ['PATH'] = path
+
+ if 'PYTHONPATH' in os.environ:
+ py_path = os.environ['PYTHONPATH']
+ py_path = "{0}:{1}".format(py_path, self.waagent_path)
+ else:
+ py_path = self.waagent_path
+ os.environ['PYTHONPATH'] = py_path
+
+ def is_sys_user(self, username):
+ #User 'core' is not a sysuser
+ if username == 'core':
+ return False
+ return super(CoreOSUtil, self).IsSysUser(username)
+
+ def is_dhcp_enabled(self):
+ return True
+
+ def start_network(self) :
+ return shellutil.run("systemctl start systemd-networkd", chk_err=False)
+
+ def restart_if(self, iface):
+ shellutil.run("systemctl restart systemd-networkd")
+
+ def restart_ssh_service(self):
+ return shellutil.run("systemctl restart sshd", chk_err=False)
+
+ def stop_dhcp_service(self):
+ return shellutil.run("systemctl stop systemd-networkd", chk_err=False)
+
+ def start_dhcp_service(self):
+ return shellutil.run("systemctl start systemd-networkd", chk_err=False)
+
+ def start_agent_service(self):
+ return shellutil.run("systemctl start wagent", chk_err=False)
+
+ def stop_agent_service(self):
+ return shellutil.run("systemctl stop wagent", chk_err=False)
+
+ def get_dhcp_pid(self):
+ ret= shellutil.run_get_output("pidof systemd-networkd")
+ return ret[1] if ret[0] == 0 else None
+
+ def decode_customdata(self, data):
+ return base64.b64decode(data)
+
diff --git a/azurelinuxagent/distro/debian/__init__.py b/azurelinuxagent/distro/debian/__init__.py
new file mode 100644
index 0000000..4b2b9e1
--- /dev/null
+++ b/azurelinuxagent/distro/debian/__init__.py
@@ -0,0 +1,19 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
diff --git a/azurelinuxagent/distro/debian/loader.py b/azurelinuxagent/distro/debian/loader.py
new file mode 100644
index 0000000..0787758
--- /dev/null
+++ b/azurelinuxagent/distro/debian/loader.py
@@ -0,0 +1,24 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+
+def get_osutil():
+ from azurelinuxagent.distro.debian.osutil import DebianOSUtil
+ return DebianOSUtil()
+
diff --git a/azurelinuxagent/distro/debian/osutil.py b/azurelinuxagent/distro/debian/osutil.py
new file mode 100644
index 0000000..a40c1de
--- /dev/null
+++ b/azurelinuxagent/distro/debian/osutil.py
@@ -0,0 +1,47 @@
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import os
+import re
+import pwd
+import shutil
+import socket
+import array
+import struct
+import fcntl
+import time
+import base64
+import azurelinuxagent.logger as logger
+import azurelinuxagent.utils.fileutil as fileutil
+import azurelinuxagent.utils.shellutil as shellutil
+import azurelinuxagent.utils.textutil as textutil
+from azurelinuxagent.distro.default.osutil import DefaultOSUtil
+
+class DebianOSUtil(DefaultOSUtil):
+ def __init__(self):
+ super(DebianOSUtil, self).__init__()
+
+ def restart_ssh_service(self):
+ return shellutil.run("service sshd restart", chk_err=False)
+
+ def stop_agent_service(self):
+ return shellutil.run("service azurelinuxagent stop", chk_err=False)
+
+ def start_agent_service(self):
+ return shellutil.run("service azurelinuxagent start", chk_err=False)
+
diff --git a/azurelinuxagent/distro/default/__init__.py b/azurelinuxagent/distro/default/__init__.py
new file mode 100644
index 0000000..4b2b9e1
--- /dev/null
+++ b/azurelinuxagent/distro/default/__init__.py
@@ -0,0 +1,19 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
diff --git a/azurelinuxagent/distro/default/deprovision.py b/azurelinuxagent/distro/default/deprovision.py
new file mode 100644
index 0000000..231f4eb
--- /dev/null
+++ b/azurelinuxagent/distro/default/deprovision.py
@@ -0,0 +1,117 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import azurelinuxagent.conf as conf
+from azurelinuxagent.utils.osutil import OSUTIL
+import azurelinuxagent.protocol as prot
+import azurelinuxagent.protocol.ovfenv as ovf
+import azurelinuxagent.utils.fileutil as fileutil
+import azurelinuxagent.utils.shellutil as shellutil
+
+class DeprovisionAction(object):
+ def __init__(self, func, args=[], kwargs={}):
+ self.func = func
+ self.args = args
+ self.kwargs = kwargs
+
+ def invoke(self):
+ self.func(*self.args, **self.kwargs)
+
+class DeprovisionHandler(object):
+
+ def del_root_password(self, warnings, actions):
+ warnings.append("WARNING! root password will be disabled. "
+ "You will not be able to login as root.")
+
+ actions.append(DeprovisionAction(OSUTIL.del_root_password))
+
+ def del_user(self, warnings, actions):
+
+ try:
+ ovfenv = ovf.get_ovf_env()
+ except prot.ProtocolError:
+ warnings.append("WARNING! ovf-env.xml is not found.")
+ warnings.append("WARNING! Skip delete user.")
+ return
+
+ username = ovfenv.username
+ warnings.append(("WARNING! {0} account and entire home directory "
+ "will be deleted.").format(username))
+ actions.append(DeprovisionAction(OSUTIL.del_account, [username]))
+
+
+ def regen_ssh_host_key(self, warnings, actions):
+ warnings.append("WARNING! All SSH host key pairs will be deleted.")
+ actions.append(DeprovisionAction(OSUTIL.set_hostname,
+ ['localhost.localdomain']))
+ actions.append(DeprovisionAction(shellutil.run,
+ ['rm -f /etc/ssh/ssh_host_*key*']))
+
+ def stop_agent_service(self, warnings, actions):
+ warnings.append("WARNING! The waagent service will be stopped.")
+ actions.append(DeprovisionAction(OSUTIL.stop_agent_service))
+
+ def del_files(self, warnings, actions):
+ files_to_del = ['/root/.bash_history', '/var/log/waagent.log']
+ actions.append(DeprovisionAction(fileutil.rm_files, files_to_del))
+
+ def del_dhcp_lease(self, warnings, actions):
+ warnings.append("WARNING! Cached DHCP leases will be deleted.")
+ dirs_to_del = ["/var/lib/dhclient", "/var/lib/dhcpcd", "/var/lib/dhcp"]
+ actions.append(DeprovisionAction(fileutil.rm_dirs, dirs_to_del))
+
+ def del_lib_dir(self, warnings, actions):
+ dirs_to_del = [OSUTIL.get_lib_dir()]
+ actions.append(DeprovisionAction(fileutil.rm_dirs, dirs_to_del))
+
+ def setup(self, deluser):
+ warnings = []
+ actions = []
+
+ self.stop_agent_service(warnings, actions)
+ if conf.get_switch("Provisioning.RegenerateSshHostkey", False):
+ self.regen_ssh_host_key(warnings, actions)
+
+ self.del_dhcp_lease(warnings, actions)
+
+ if conf.get_switch("Provisioning.DeleteRootPassword", False):
+ self.del_root_password(warnings, actions)
+
+ self.del_lib_dir(warnings, actions)
+ self.del_files(warnings, actions)
+
+ if deluser:
+ self.del_user(warnings, actions)
+
+ return warnings, actions
+
+ def deprovision(self, force=False, deluser=False):
+ warnings, actions = self.setup(deluser)
+ for warning in warnings:
+ print(warning)
+
+ if not force:
+ confirm = input("Do you want to proceed (y/n)")
+ if not confirm.lower().startswith('y'):
+ return
+
+ for action in actions:
+ action.invoke()
+
+
diff --git a/azurelinuxagent/distro/default/dhcp.py b/azurelinuxagent/distro/default/dhcp.py
new file mode 100644
index 0000000..574ebd4
--- /dev/null
+++ b/azurelinuxagent/distro/default/dhcp.py
@@ -0,0 +1,330 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+import os
+import socket
+import array
+import time
+import azurelinuxagent.logger as logger
+from azurelinuxagent.utils.osutil import OSUTIL
+from azurelinuxagent.exception import AgentNetworkError
+import azurelinuxagent.utils.fileutil as fileutil
+import azurelinuxagent.utils.shellutil as shellutil
+from azurelinuxagent.utils.textutil import *
+
+WIRE_SERVER_ADDR_FILE_NAME="WireServer"
+
+class DhcpHandler(object):
+ def __init__(self):
+ self.endpoint = None
+ self.gateway = None
+ self.routes = None
+
+ def wait_for_network(self):
+ ipv4 = OSUTIL.get_ip4_addr()
+ while ipv4 == '' or ipv4 == '0.0.0.0':
+ logger.info("Waiting for network.")
+ time.sleep(10)
+ OSUTIL.start_network()
+ ipv4 = OSUTIL.get_ip4_addr()
+
+ def probe(self):
+ logger.info("Send dhcp request")
+ self.wait_for_network()
+ mac_addr = OSUTIL.get_mac_addr()
+ req = build_dhcp_request(mac_addr)
+ resp = send_dhcp_request(req)
+ if resp is None:
+ logger.warn("Failed to detect wire server.")
+ return
+ endpoint, gateway, routes = parse_dhcp_resp(resp)
+ self.endpoint = endpoint
+ logger.info("Wire server endpoint:{0}", endpoint)
+ logger.info("Gateway:{0}", gateway)
+ logger.info("Routes:{0}", routes)
+ if endpoint is not None:
+ path = os.path.join(OSUTIL.get_lib_dir(), WIRE_SERVER_ADDR_FILE_NAME)
+ fileutil.write_file(path, endpoint)
+ self.gateway = gateway
+ self.routes = routes
+ self.conf_routes()
+
+ def get_endpoint(self):
+ return self.endpoint
+
+ def conf_routes(self):
+ logger.info("Configure routes")
+ #Add default gateway
+ if self.gateway is not None:
+ OSUTIL.route_add(0 , 0, self.gateway)
+ if self.routes is not None:
+ for route in self.routes:
+ OSUTIL.route_add(route[0], route[1], route[2])
+
+def validate_dhcp_resp(request, response):
+ bytes_recv = len(response)
+ if bytes_recv < 0xF6:
+ logger.error("HandleDhcpResponse: Too few bytes received:{0}",
+ bytes_recv)
+ return False
+
+ logger.verb("BytesReceived:{0}", hex(bytes_recv))
+ logger.verb("DHCP response:{0}", hex_dump(response, bytes_recv))
+
+ # check transactionId, cookie, MAC address cookie should never mismatch
+ # transactionId and MAC address may mismatch if we see a response
+ # meant from another machine
+ if not compare_bytes(request, response, 0xEC, 4):
+ logger.verb("Cookie not match:\nsend={0},\nreceive={1}",
+ hex_dump3(request, 0xEC, 4),
+ hex_dump3(response, 0xEC, 4))
+ raise AgentNetworkError("Cookie in dhcp respones "
+ "doesn't match the request")
+
+ if not compare_bytes(request, response, 4, 4):
+ logger.verb("TransactionID not match:\nsend={0},\nreceive={1}",
+ hex_dump3(request, 4, 4),
+ hex_dump3(response, 4, 4))
+ raise AgentNetworkError("TransactionID in dhcp respones "
+ "doesn't match the request")
+
+ if not compare_bytes(request, response, 0x1C, 6):
+ logger.verb("Mac Address not match:\nsend={0},\nreceive={1}",
+ hex_dump3(request, 0x1C, 6),
+ hex_dump3(response, 0x1C, 6))
+ raise AgentNetworkError("Mac Addr in dhcp respones "
+ "doesn't match the request")
+
+def parse_route(response, option, i, length, bytes_recv):
+ # http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+ logger.verb("Routes at offset: {0} with length:{1}",
+ hex(i),
+ hex(length))
+ routes = []
+ if length < 5:
+ logger.error("Data too small for option:{0}", option)
+ j = i + 2
+ while j < (i + length + 2):
+ mask_len_bits = str_to_ord(response[j])
+ mask_len_bytes = (((mask_len_bits + 7) & ~7) >> 3)
+ mask = 0xFFFFFFFF & (0xFFFFFFFF << (32 - mask_len_bits))
+ j += 1
+ net = unpack_big_endian(response, j, mask_len_bytes)
+ net <<= (32 - mask_len_bytes * 8)
+ net &= mask
+ j += mask_len_bytes
+ gateway = unpack_big_endian(response, j, 4)
+ j += 4
+ routes.append((net, mask, gateway))
+ if j != (i + length + 2):
+ logger.error("Unable to parse routes")
+ return routes
+
+def parse_ip_addr(response, option, i, length, bytes_recv):
+ if i + 5 < bytes_recv:
+ if length != 4:
+ logger.error("Endpoint or Default Gateway not 4 bytes")
+ return None
+ addr = unpack_big_endian(response, i + 2, 4)
+ ip_addr = int_to_ip4_addr(addr)
+ return ip_addr
+ else:
+ logger.error("Data too small for option:{0}", option)
+ return None
+
+def parse_dhcp_resp(response):
+ """
+ Parse DHCP response:
+ Returns endpoint server or None on error.
+ """
+ logger.verb("parse Dhcp Response")
+ bytes_recv = len(response)
+ endpoint = None
+ gateway = None
+ routes = None
+
+ # Walk all the returned options, parsing out what we need, ignoring the
+ # others. We need the custom option 245 to find the the endpoint we talk to,
+ # as well as, to handle some Linux DHCP client incompatibilities,
+ # options 3 for default gateway and 249 for routes. And 255 is end.
+
+ i = 0xF0 # offset to first option
+ while i < bytes_recv:
+ option = str_to_ord(response[i])
+ length = 0
+ if (i + 1) < bytes_recv:
+ length = str_to_ord(response[i + 1])
+ logger.verb("DHCP option {0} at offset:{1} with length:{2}",
+ hex(option),
+ hex(i),
+ hex(length))
+ if option == 255:
+ logger.verb("DHCP packet ended at offset:{0}", hex(i))
+ break
+ elif option == 249:
+ routes = parse_route(response, option, i, length, bytes_recv)
+ elif option == 3:
+ gateway = parse_ip_addr(response, option, i, length, bytes_recv)
+ logger.verb("Default gateway:{0}, at {1}",
+ gateway,
+ hex(i))
+ elif option == 245:
+ endpoint = parse_ip_addr(response, option, i, length, bytes_recv)
+ logger.verb("Azure wire protocol endpoint:{0}, at {1}",
+ gateway,
+ hex(i))
+ else:
+ logger.verb("Skipping DHCP option:{0} at {1} with length {2}",
+ hex(option),
+ hex(i),
+ hex(length))
+ i += length + 2
+ return endpoint, gateway, routes
+
+
+def allow_dhcp_broadcast(func):
+ """
+ Temporary allow broadcase for dhcp. Remove the route when done.
+ """
+ def wrapper(*args, **kwargs):
+ missing_default_route = OSUTIL.is_missing_default_route()
+ ifname = OSUTIL.get_if_name()
+ if missing_default_route:
+ OSUTIL.set_route_for_dhcp_broadcast(ifname)
+ result = func(*args, **kwargs)
+ if missing_default_route:
+ OSUTIL.remove_route_for_dhcp_broadcast(ifname)
+ return result
+ return wrapper
+
+def disable_dhcp_service(func):
+ """
+ In some distros, dhcp service needs to be shutdown before agent probe
+ endpoint through dhcp.
+ """
+ def wrapper(*args, **kwargs):
+ if OSUTIL.is_dhcp_enabled():
+ OSUTIL.stop_dhcp_service()
+ result = func(*args, **kwargs)
+ OSUTIL.start_dhcp_service()
+ return result
+ else:
+ return func(*args, **kwargs)
+ return wrapper
+
+
+@allow_dhcp_broadcast
+@disable_dhcp_service
+def send_dhcp_request(request):
+ __waiting_duration__ = [0, 10, 30, 60, 60]
+ for duration in __waiting_duration__:
+ try:
+ OSUTIL.allow_dhcp_broadcast()
+ response = socket_send(request)
+ validate_dhcp_resp(request, response)
+ return response
+ except AgentNetworkError as e:
+ logger.warn("Failed to send DHCP request: {0}", e)
+ time.sleep(duration)
+ return None
+
+def socket_send(request):
+ sock = None
+ try:
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM,
+ socket.IPPROTO_UDP)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ sock.bind(("0.0.0.0", 68))
+ sock.sendto(request, ("<broadcast>", 67))
+ sock.settimeout(10)
+ logger.verb("Send DHCP request: Setting socket.timeout=10, "
+ "entering recv")
+ response = sock.recv(1024)
+ return response
+ except IOError as e:
+ raise AgentNetworkError("{0}".format(e))
+ finally:
+ if sock is not None:
+ sock.close()
+
+def build_dhcp_request(mac_addr):
+ """
+ Build DHCP request string.
+ """
+ #
+ # typedef struct _DHCP {
+ # UINT8 Opcode; /* op: BOOTREQUEST or BOOTREPLY */
+ # UINT8 HardwareAddressType; /* htype: ethernet */
+ # UINT8 HardwareAddressLength; /* hlen: 6 (48 bit mac address) */
+ # UINT8 Hops; /* hops: 0 */
+ # UINT8 TransactionID[4]; /* xid: random */
+ # UINT8 Seconds[2]; /* secs: 0 */
+ # UINT8 Flags[2]; /* flags: 0 or 0x8000 for broadcast */
+ # UINT8 ClientIpAddress[4]; /* ciaddr: 0 */
+ # UINT8 YourIpAddress[4]; /* yiaddr: 0 */
+ # UINT8 ServerIpAddress[4]; /* siaddr: 0 */
+ # UINT8 RelayAgentIpAddress[4]; /* giaddr: 0 */
+ # UINT8 ClientHardwareAddress[16]; /* chaddr: 6 byte eth MAC address */
+ # UINT8 ServerName[64]; /* sname: 0 */
+ # UINT8 BootFileName[128]; /* file: 0 */
+ # UINT8 MagicCookie[4]; /* 99 130 83 99 */
+ # /* 0x63 0x82 0x53 0x63 */
+ # /* options -- hard code ours */
+ #
+ # UINT8 MessageTypeCode; /* 53 */
+ # UINT8 MessageTypeLength; /* 1 */
+ # UINT8 MessageType; /* 1 for DISCOVER */
+ # UINT8 End; /* 255 */
+ # } DHCP;
+ #
+
+ # tuple of 244 zeros
+ # (struct.pack_into would be good here, but requires Python 2.5)
+ request = [0] * 244
+
+ trans_id = gen_trans_id()
+
+ # Opcode = 1
+ # HardwareAddressType = 1 (ethernet/MAC)
+ # HardwareAddressLength = 6 (ethernet/MAC/48 bits)
+ for a in range(0, 3):
+ request[a] = [1, 1, 6][a]
+
+ # fill in transaction id (random number to ensure response matches request)
+ for a in range(0, 4):
+ request[4 + a] = str_to_ord(trans_id[a])
+
+ logger.verb("BuildDhcpRequest: transactionId:%s,%04X" % (
+ hex_dump2(trans_id),
+ unpack_big_endian(request, 4, 4)))
+
+ # fill in ClientHardwareAddress
+ for a in range(0, 6):
+ request[0x1C + a] = str_to_ord(mac_addr[a])
+
+ # DHCP Magic Cookie: 99, 130, 83, 99
+ # MessageTypeCode = 53 DHCP Message Type
+ # MessageTypeLength = 1
+ # MessageType = DHCPDISCOVER
+ # End = 255 DHCP_END
+ for a in range(0, 8):
+ request[0xEC + a] = [99, 130, 83, 99, 53, 1, 1, 255][a]
+ return array.array("B", request)
+
+def gen_trans_id():
+ return os.urandom(4)
diff --git a/azurelinuxagent/distro/default/env.py b/azurelinuxagent/distro/default/env.py
new file mode 100644
index 0000000..6a67113
--- /dev/null
+++ b/azurelinuxagent/distro/default/env.py
@@ -0,0 +1,115 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import os
+import socket
+import threading
+import time
+import azurelinuxagent.logger as logger
+import azurelinuxagent.conf as conf
+from azurelinuxagent.utils.osutil import OSUTIL
+
+class EnvHandler(object):
+ """
+ Monitor changes to dhcp and hostname.
+ If dhcp clinet process re-start has occurred, reset routes, dhcp with fabric.
+
+ Monitor scsi disk.
+ If new scsi disk found, set
+ """
+ def __init__(self, handlers):
+ self.monitor = EnvMonitor(handlers.dhcp_handler)
+
+ def start(self):
+ self.monitor.start()
+
+ def stop(self):
+ self.monitor.stop()
+
+class EnvMonitor(object):
+
+ def __init__(self, dhcp_handler):
+ self.dhcp_handler = dhcp_handler
+ self.stopped = True
+ self.hostname = None
+ self.dhcpid = None
+ self.server_thread=None
+
+ def start(self):
+ if not self.stopped:
+ logger.info("Stop existing env monitor service.")
+ self.stop()
+
+ self.stopped = False
+ logger.info("Start env monitor service.")
+ self.hostname = socket.gethostname()
+ self.dhcpid = OSUTIL.get_dhcp_pid()
+ self.server_thread = threading.Thread(target = self.monitor)
+ self.server_thread.setDaemon(True)
+ self.server_thread.start()
+
+ def monitor(self):
+ """
+ Monitor dhcp client pid and hostname.
+ If dhcp clinet process re-start has occurred, reset routes.
+ """
+ while not self.stopped:
+ OSUTIL.remove_rules_files()
+ timeout = conf.get("OS.RootDeviceScsiTimeout", None)
+ if timeout is not None:
+ OSUTIL.set_scsi_disks_timeout(timeout)
+ if conf.get_switch("Provisioning.MonitorHostName", False):
+ self.handle_hostname_update()
+ self.handle_dhclient_restart()
+ time.sleep(5)
+
+ def handle_hostname_update(self):
+ curr_hostname = socket.gethostname()
+ if curr_hostname != self.hostname:
+ logger.info("EnvMonitor: Detected host name change: {0} -> {1}",
+ self.hostname, curr_hostname)
+ OSUTIL.set_hostname(curr_hostname)
+ OSUTIL.publish_hostname(curr_hostname)
+ self.hostname = curr_hostname
+
+ def handle_dhclient_restart(self):
+ if self.dhcpid is None:
+ logger.warn("Dhcp client is not running. ")
+ self.dhcpid = OSUTIL.get_dhcp_pid()
+ return
+
+ #The dhcp process hasn't changed since last check
+ if os.path.isdir(os.path.join('/proc', self.dhcpid.strip())):
+ return
+
+ newpid = OSUTIL.get_dhcp_pid()
+ if newpid is not None and newpid != self.dhcpid:
+ logger.info("EnvMonitor: Detected dhcp client restart. "
+ "Restoring routing table.")
+ self.dhcp_handler.conf_routes()
+ self.dhcpid = newpid
+
+ def stop(self):
+ """
+ Stop server comminucation and join the thread to main thread.
+ """
+ self.stopped = True
+ if self.server_thread is not None:
+ self.server_thread.join()
+
diff --git a/azurelinuxagent/distro/default/extension.py b/azurelinuxagent/distro/default/extension.py
new file mode 100644
index 0000000..58ba84e
--- /dev/null
+++ b/azurelinuxagent/distro/default/extension.py
@@ -0,0 +1,647 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+import os
+import zipfile
+import time
+import json
+import subprocess
+import azurelinuxagent.logger as logger
+from azurelinuxagent.future import text
+from azurelinuxagent.utils.osutil import OSUTIL
+import azurelinuxagent.protocol as prot
+from azurelinuxagent.event import add_event, WALAEventOperation
+from azurelinuxagent.exception import ExtensionError
+import azurelinuxagent.utils.fileutil as fileutil
+import azurelinuxagent.utils.restutil as restutil
+import azurelinuxagent.utils.shellutil as shellutil
+
+VALID_EXTENSION_STATUS = ['transitioning', 'error', 'success', 'warning']
+
+def validate_has_key(obj, key, fullname):
+ if key not in obj:
+ raise ExtensionError("Missing: {0}".format(fullname))
+
+def validate_in_range(val, valid_range, name):
+ if val not in valid_range:
+ raise ExtensionError("Invalid {0}: {1}".format(name, val))
+
+def try_get(dictionary, key, default=None):
+ try:
+ return dictionary[key]
+ except KeyError:
+ return default
+
+def extension_sub_status_to_v2(substatus):
+ #Check extension sub status format
+ validate_has_key(substatus, 'name', 'substatus/name')
+ validate_has_key(substatus, 'status', 'substatus/status')
+ validate_has_key(substatus, 'code', 'substatus/code')
+ validate_has_key(substatus, 'formattedMessage', 'substatus/formattedMessage')
+ validate_has_key(substatus['formattedMessage'], 'lang',
+ 'substatus/formattedMessage/lang')
+ validate_has_key(substatus['formattedMessage'], 'message',
+ 'substatus/formattedMessage/message')
+
+ validate_in_range(substatus['status'], VALID_EXTENSION_STATUS,
+ 'substatus/status')
+ status = prot.ExtensionSubStatus()
+ status.name = try_get(substatus, 'name')
+ status.status = try_get(substatus, 'status')
+ status.code = try_get(substatus, 'code')
+ status.message = try_get(substatus['formattedMessage'], 'message')
+ return status
+
+def ext_status_to_v2(ext_status, seq_no):
+ #Check extension status format
+ validate_has_key(ext_status, 'status', 'status')
+ validate_has_key(ext_status['status'], 'status', 'status/status')
+ validate_has_key(ext_status['status'], 'operation', 'status/operation')
+ validate_has_key(ext_status['status'], 'code', 'status/code')
+ validate_has_key(ext_status['status'], 'name', 'status/name')
+ validate_has_key(ext_status['status'], 'formattedMessage',
+ 'status/formattedMessage')
+ validate_has_key(ext_status['status']['formattedMessage'], 'lang',
+ 'status/formattedMessage/lang')
+ validate_has_key(ext_status['status']['formattedMessage'], 'message',
+ 'status/formattedMessage/message')
+
+ validate_in_range(ext_status['status']['status'], VALID_EXTENSION_STATUS,
+ 'status/status')
+
+ status = prot.ExtensionStatus()
+ status.name = try_get(ext_status['status'], 'name')
+ status.configurationAppliedTime = try_get(ext_status['status'],
+ 'configurationAppliedTime')
+ status.operation = try_get(ext_status['status'], 'operation')
+ status.status = try_get(ext_status['status'], 'status')
+ status.code = try_get(ext_status['status'], 'code')
+ status.message = try_get(ext_status['status']['formattedMessage'], 'message')
+ status.sequenceNumber = seq_no
+
+ substatus_list = try_get(ext_status['status'], 'substatus', [])
+ for substatus in substatus_list:
+ status.substatusList.extend(extension_sub_status_to_v2(substatus))
+ return status
+
+class ExtensionsHandler(object):
+
+ def process(self):
+ protocol = prot.FACTORY.get_default_protocol()
+ ext_list = protocol.get_extensions()
+
+ h_status_list = []
+ for extension in ext_list.extensions:
+ #TODO handle extension in parallel
+ pkg_list = protocol.get_extension_pkgs(extension)
+ h_status = self.process_extension(extension, pkg_list)
+ h_status_list.append(h_status)
+
+ return h_status_list
+
+ def process_extension(self, extension, pkg_list):
+ installed_version = get_installed_version(extension.name)
+ if installed_version is not None:
+ ext = ExtensionInstance(extension, pkg_list,
+ installed_version, installed=True)
+ else:
+ ext = ExtensionInstance(extension, pkg_list,
+ extension.properties.version)
+ try:
+ ext.init_logger()
+ ext.handle()
+ status = ext.collect_handler_status()
+ except ExtensionError as e:
+ logger.error("Failed to handle extension: {0}-{1}\n {2}",
+ ext.get_name(), ext.get_version(), e)
+ add_event(name=ext.get_name(), is_success=False,
+ op=ext.get_curr_op(), message = text(e))
+ ext_status = prot.ExtensionStatus(status='error', code='-1',
+ operation = ext.get_curr_op(),
+ message = text(e),
+ seq_no = ext.get_seq_no())
+ status = ext.create_handler_status(ext_status)
+ status.status = "Ready"
+ return status
+
+def parse_extension_dirname(dirname):
+ """
+ Parse installed extension dir name. Sample: ExtensionName-Version/
+ """
+ seprator = dirname.rfind('-')
+ if seprator < 0:
+ raise ExtensionError("Invalid extenation dir name")
+ return dirname[0:seprator], dirname[seprator + 1:]
+
+def get_installed_version(target_name):
+ """
+ Return the highest version instance with the same name
+ """
+ installed_version = None
+ lib_dir = OSUTIL.get_lib_dir()
+ for dir_name in os.listdir(lib_dir):
+ path = os.path.join(lib_dir, dir_name)
+ if os.path.isdir(path) and dir_name.startswith(target_name):
+ name, version = parse_extension_dirname(dir_name)
+ #Here we need to ensure names are exactly the same.
+ if name == target_name:
+ if installed_version is None or installed_version < version:
+ installed_version = version
+ return installed_version
+
+class ExtensionInstance(object):
+ def __init__(self, extension, pkg_list, curr_version, installed=False):
+ self.extension = extension
+ self.pkg_list = pkg_list
+ self.curr_version = curr_version
+ self.lib_dir = OSUTIL.get_lib_dir()
+ self.installed = installed
+ self.settings = None
+
+ #Extension will have no more than 1 settings instance
+ if len(extension.properties.extensions) > 0:
+ self.settings = extension.properties.extensions[0]
+ self.enabled = False
+ self.curr_op = None
+
+ prefix = "[{0}]".format(self.get_full_name())
+ self.logger = logger.Logger(logger.DEFAULT_LOGGER, prefix)
+
+ def init_logger(self):
+ #Init logger appender for extension
+ fileutil.mkdir(self.get_log_dir(), mode=0o700)
+ log_file = os.path.join(self.get_log_dir(), "CommandExecution.log")
+ self.logger.add_appender(logger.AppenderType.FILE,
+ logger.LogLevel.INFO, log_file)
+
+ def handle(self):
+ self.logger.info("Process extension settings:")
+ self.logger.info(" Name: {0}", self.get_name())
+ self.logger.info(" Version: {0}", self.get_version())
+
+ if self.installed:
+ self.logger.info("Installed version:{0}", self.curr_version)
+ h_status = self.get_handler_status()
+ self.enabled = (h_status == "Ready")
+
+ state = self.get_state()
+ if state == 'enabled':
+ self.handle_enable()
+ elif state == 'disabled':
+ self.handle_disable()
+ elif state == 'uninstall':
+ self.handle_disable()
+ self.handle_uninstall()
+ else:
+ raise ExtensionError("Unknown extension state:{0}".format(state))
+
+ def handle_enable(self):
+ target_version = self.get_target_version()
+ if self.installed:
+ if target_version > self.curr_version:
+ self.upgrade(target_version)
+ elif target_version == self.curr_version:
+ self.enable()
+ else:
+ raise ExtensionError("A newer version has already been installed")
+ else:
+ if target_version > self.get_version():
+ #This will happen when auto upgrade policy is enabled
+ self.logger.info("Auto upgrade to new version:{0}",
+ target_version)
+ self.curr_version = target_version
+ self.download()
+ self.init_dir()
+ self.install()
+ self.enable()
+
+ def handle_disable(self):
+ if not self.installed or not self.enabled:
+ return
+ self.disable()
+
+ def handle_uninstall(self):
+ if not self.installed:
+ return
+ self.uninstall()
+
+ def upgrade(self, target_version):
+ self.logger.info("Upgrade from: {0} to {1}", self.curr_version,
+ target_version)
+ self.curr_op=WALAEventOperation.Upgrade
+ old = self
+ new = ExtensionInstance(self.extension, self.pkg_list, target_version)
+ self.logger.info("Download new extension package")
+ new.init_logger()
+ new.download()
+ self.logger.info("Initialize new extension directory")
+ new.init_dir()
+
+ old.disable()
+ self.logger.info("Update new extension")
+ new.update()
+ old.uninstall()
+ man = new.load_manifest()
+ if man.is_update_with_install():
+ self.logger.info("Install new extension")
+ new.install()
+ self.logger.info("Enable new extension")
+ new.enable()
+ add_event(name=self.get_name(), is_success=True,
+ op=self.curr_op, message="")
+
+ def download(self):
+ self.logger.info("Download extension package")
+ self.curr_op=WALAEventOperation.Download
+ uris = self.get_package_uris()
+ package = None
+ for uri in uris:
+ try:
+ resp = restutil.http_get(uri.uri, chk_proxy=True)
+ package = resp.read()
+ break
+ except restutil.HttpError as e:
+ self.logger.warn("Failed download extension from: {0}", uri.uri)
+
+ if package is None:
+ raise ExtensionError("Download extension failed")
+
+ self.logger.info("Unpack extension package")
+ pkg_file = os.path.join(self.lib_dir, os.path.basename(uri.uri) + ".zip")
+ fileutil.write_file(pkg_file, bytearray(package), asbin=True)
+ zipfile.ZipFile(pkg_file).extractall(self.get_base_dir())
+ chmod = "find {0} -type f | xargs chmod u+x".format(self.get_base_dir())
+ shellutil.run(chmod)
+ add_event(name=self.get_name(), is_success=True,
+ op=self.curr_op, message="")
+
+ def init_dir(self):
+ self.logger.info("Initialize extension directory")
+ #Save HandlerManifest.json
+ man_file = fileutil.search_file(self.get_base_dir(),
+ 'HandlerManifest.json')
+ man = fileutil.read_file(man_file, remove_bom=True)
+ fileutil.write_file(self.get_manifest_file(), man)
+
+ #Create status and config dir
+ status_dir = self.get_status_dir()
+ fileutil.mkdir(status_dir, mode=0o700)
+ conf_dir = self.get_conf_dir()
+ fileutil.mkdir(conf_dir, mode=0o700)
+
+ #Init handler state to uninstall
+ self.set_handler_status("NotReady")
+
+ #Save HandlerEnvironment.json
+ self.create_handler_env()
+
+ def enable(self):
+ self.logger.info("Enable extension.")
+ self.curr_op=WALAEventOperation.Enable
+ man = self.load_manifest()
+ self.launch_command(man.get_enable_command())
+ self.set_handler_status("Ready")
+ add_event(name=self.get_name(), is_success=True,
+ op=self.curr_op, message="")
+
+ def disable(self):
+ self.logger.info("Disable extension.")
+ self.curr_op=WALAEventOperation.Disable
+ man = self.load_manifest()
+ self.launch_command(man.get_disable_command(), timeout=900)
+ self.set_handler_status("Ready")
+ add_event(name=self.get_name(), is_success=True,
+ op=self.curr_op, message="")
+
+ def install(self):
+ self.logger.info("Install extension.")
+ self.curr_op=WALAEventOperation.Install
+ man = self.load_manifest()
+ self.set_handler_status("Installing")
+ self.launch_command(man.get_install_command(), timeout=900)
+ self.set_handler_status("Ready")
+ add_event(name=self.get_name(), is_success=True,
+ op=self.curr_op, message="")
+
+ def uninstall(self):
+ self.logger.info("Uninstall extension.")
+ self.curr_op=WALAEventOperation.UnInstall
+ man = self.load_manifest()
+ self.launch_command(man.get_uninstall_command())
+ self.set_handler_status("NotReady")
+ add_event(name=self.get_name(), is_success=True,
+ op=self.curr_op, message="")
+
+ def update(self):
+ self.logger.info("Update extension.")
+ self.curr_op=WALAEventOperation.Update
+ man = self.load_manifest()
+ self.launch_command(man.get_update_command(), timeout=900)
+ add_event(name=self.get_name(), is_success=True,
+ op=self.curr_op, message="")
+
+ def create_handler_status(self, ext_status, heartbeat=None):
+ status = prot.ExtensionHandlerStatus()
+ status.handlerName = self.get_name()
+ status.handlerVersion = self.get_version()
+ status.status = self.get_handler_status()
+ status.extensionStatusList.append(ext_status)
+ return status
+
+ def collect_handler_status(self):
+ man = self.load_manifest()
+ heartbeat=None
+ if man.is_report_heartbeat():
+ heartbeat = self.collect_heartbeat()
+ ext_status = self.collect_extension_status()
+ status= self.create_handler_status(ext_status, heartbeat)
+ status.status = self.get_handler_status()
+ if heartbeat is not None:
+ status.status = heartbeat['status']
+ status.extensionStatusList.append(ext_status)
+ return status
+
+ def collect_extension_status(self):
+ ext_status_file = self.get_status_file()
+ try:
+ ext_status_str = fileutil.read_file(ext_status_file)
+ ext_status = json.loads(ext_status_str)
+ except IOError as e:
+ raise ExtensionError("Failed to get status file: {0}".format(e))
+ except ValueError as e:
+ raise ExtensionError("Malformed status file: {0}".format(e))
+ return ext_status_to_v2(ext_status[0],
+ self.settings.sequenceNumber)
+
+ def get_handler_status(self):
+ h_status = "uninstalled"
+ h_status_file = self.get_handler_state_file()
+ try:
+ h_status = fileutil.read_file(h_status_file)
+ return h_status
+ except IOError as e:
+ raise ExtensionError("Failed to get handler status: {0}".format(e))
+
+ def set_handler_status(self, status):
+ h_status_file = self.get_handler_state_file()
+ try:
+ fileutil.write_file(h_status_file, status)
+ except IOError as e:
+ raise ExtensionError("Failed to set handler status: {0}".format(e))
+
+ def collect_heartbeat(self):
+ self.logger.info("Collect heart beat")
+ heartbeat_file = os.path.join(OSUTIL.get_lib_dir(),
+ self.get_heartbeat_file())
+ if not os.path.isfile(heartbeat_file):
+ raise ExtensionError("Failed to get heart beat file")
+ if not self.is_responsive(heartbeat_file):
+ return {
+ "status": "Unresponsive",
+ "code": -1,
+ "message": "Extension heartbeat is not responsive"
+ }
+ try:
+ heartbeat_json = fileutil.read_file(heartbeat_file)
+ heartbeat = json.loads(heartbeat_json)[0]['heartbeat']
+ except IOError as e:
+ raise ExtensionError("Failed to get heartbeat file:{0}".format(e))
+ except ValueError as e:
+ raise ExtensionError("Malformed heartbeat file: {0}".format(e))
+ return heartbeat
+
+ def is_responsive(self, heartbeat_file):
+ last_update=int(time.time()-os.stat(heartbeat_file).st_mtime)
+ return last_update > 600 # not updated for more than 10 min
+
+ def launch_command(self, cmd, timeout=300):
+ self.logger.info("Launch command:{0}", cmd)
+ base_dir = self.get_base_dir()
+ self.update_settings()
+ try:
+ devnull = open(os.devnull, 'w')
+ child = subprocess.Popen(base_dir + "/" + cmd, shell=True,
+ cwd=base_dir, stdout=devnull)
+ except Exception as e:
+ #TODO do not catch all exception
+ raise ExtensionError("Failed to launch: {0}, {1}".format(cmd, e))
+
+ retry = timeout / 5
+ while retry > 0 and child.poll == None:
+ time.sleep(5)
+ retry -= 1
+ if retry == 0:
+ os.kill(child.pid, 9)
+ raise ExtensionError("Timeout({0}): {1}".format(timeout, cmd))
+
+ ret = child.wait()
+ if ret == None or ret != 0:
+ raise ExtensionError("Non-zero exit code: {0}, {1}".format(ret, cmd))
+
+ def load_manifest(self):
+ man_file = self.get_manifest_file()
+ try:
+ data = json.loads(fileutil.read_file(man_file))
+ except IOError as e:
+ raise ExtensionError('Failed to load manifest file.')
+ except ValueError as e:
+ raise ExtensionError('Malformed manifest file.')
+
+ return HandlerManifest(data[0])
+
+
+ def update_settings(self):
+ if self.settings is None:
+ self.logger.verbose("Extension has no settings")
+ return
+
+ settings = {
+ 'publicSettings': self.settings.publicSettings,
+ 'protectedSettings': self.settings.privateSettings,
+ 'protectedSettingsCertThumbprint': self.settings.certificateThumbprint
+ }
+ ext_settings = {
+ "runtimeSettings":[{
+ "handlerSettings": settings
+ }]
+ }
+ fileutil.write_file(self.get_settings_file(), json.dumps(ext_settings))
+
+ latest = os.path.join(self.get_conf_dir(), "latest")
+ fileutil.write_file(latest, self.settings.sequenceNumber)
+
+ def create_handler_env(self):
+ env = [{
+ "name": self.get_name(),
+ "version" : self.get_version(),
+ "handlerEnvironment" : {
+ "logFolder" : self.get_log_dir(),
+ "configFolder" : self.get_conf_dir(),
+ "statusFolder" : self.get_status_dir(),
+ "heartbeatFile" : self.get_heartbeat_file()
+ }
+ }]
+ fileutil.write_file(self.get_env_file(),
+ json.dumps(env))
+
+ def get_target_version(self):
+ version = self.get_version()
+ update_policy = self.get_upgrade_policy()
+ if update_policy is None or update_policy.lower() != 'auto':
+ return version
+
+ major = version.split('.')[0]
+ if major is None:
+ raise ExtensionError("Wrong version format: {0}".format(version))
+
+ packages = [x for x in self.pkg_list.versions if x.version.startswith(major + ".")]
+ packages = sorted(packages, key=lambda x: x.version, reverse=True)
+ if len(packages) <= 0:
+ raise ExtensionError("Can't find version: {0}.*".format(major))
+
+ return packages[0].version
+
+ def get_package_uris(self):
+ version = self.get_version()
+ packages = self.pkg_list.versions
+ if packages is None:
+ raise ExtensionError("Package uris is None.")
+
+ for package in packages:
+ if package.version == version:
+ return package.uris
+
+ raise ExtensionError("Can't get package uris for {0}.".format(version))
+
+ def get_curr_op(self):
+ return self.curr_op
+
+ def get_name(self):
+ return self.extension.name
+
+ def get_version(self):
+ return self.extension.properties.version
+
+ def get_state(self):
+ return self.extension.properties.state
+
+ def get_seq_no(self):
+ return self.settings.sequenceNumber
+
+ def get_upgrade_policy(self):
+ return self.extension.properties.upgradePolicy
+
+ def get_full_name(self):
+ return "{0}-{1}".format(self.get_name(), self.curr_version)
+
+ def get_base_dir(self):
+ return os.path.join(OSUTIL.get_lib_dir(), self.get_full_name())
+
+ def get_status_dir(self):
+ return os.path.join(self.get_base_dir(), "status")
+
+ def get_status_file(self):
+ return os.path.join(self.get_status_dir(),
+ "{0}.status".format(self.settings.sequenceNumber))
+
+ def get_conf_dir(self):
+ return os.path.join(self.get_base_dir(), 'config')
+
+ def get_settings_file(self):
+ return os.path.join(self.get_conf_dir(),
+ "{0}.settings".format(self.settings.sequenceNumber))
+
+ def get_handler_state_file(self):
+ return os.path.join(self.get_conf_dir(), 'HandlerState')
+
+ def get_heartbeat_file(self):
+ return os.path.join(self.get_base_dir(), 'heartbeat.log')
+
+ def get_manifest_file(self):
+ return os.path.join(self.get_base_dir(), 'HandlerManifest.json')
+
+ def get_env_file(self):
+ return os.path.join(self.get_base_dir(), 'HandlerEnvironment.json')
+
+ def get_log_dir(self):
+ return os.path.join(OSUTIL.get_ext_log_dir(), self.get_name(),
+ self.curr_version)
+
+class HandlerEnvironment(object):
+ def __init__(self, data):
+ self.data = data
+
+ def get_version(self):
+ return self.data["version"]
+
+ def get_log_dir(self):
+ return self.data["handlerEnvironment"]["logFolder"]
+
+ def get_conf_dir(self):
+ return self.data["handlerEnvironment"]["configFolder"]
+
+ def get_status_dir(self):
+ return self.data["handlerEnvironment"]["statusFolder"]
+
+ def get_heartbeat_file(self):
+ return self.data["handlerEnvironment"]["heartbeatFile"]
+
+class HandlerManifest(object):
+ def __init__(self, data):
+ if data is None or data['handlerManifest'] is None:
+ raise ExtensionError('Malformed manifest file.')
+ self.data = data
+
+ def get_name(self):
+ return self.data["name"]
+
+ def get_version(self):
+ return self.data["version"]
+
+ def get_install_command(self):
+ return self.data['handlerManifest']["installCommand"]
+
+ def get_uninstall_command(self):
+ return self.data['handlerManifest']["uninstallCommand"]
+
+ def get_update_command(self):
+ return self.data['handlerManifest']["updateCommand"]
+
+ def get_enable_command(self):
+ return self.data['handlerManifest']["enableCommand"]
+
+ def get_disable_command(self):
+ return self.data['handlerManifest']["disableCommand"]
+
+ def is_reboot_after_install(self):
+ #TODO handle reboot after install
+ if "rebootAfterInstall" not in self.data['handlerManifest']:
+ return False
+ return self.data['handlerManifest']["rebootAfterInstall"]
+
+ def is_report_heartbeat(self):
+ if "reportHeartbeat" not in self.data['handlerManifest']:
+ return False
+ return self.data['handlerManifest']["reportHeartbeat"]
+
+ def is_update_with_install(self):
+ if "updateMode" not in self.data['handlerManifest']:
+ return False
+ if "updateMode" in self.data:
+ return self.data['handlerManifest']["updateMode"].lower() == "updatewithinstall"
+ return False
diff --git a/azurelinuxagent/distro/default/handlerFactory.py b/azurelinuxagent/distro/default/handlerFactory.py
new file mode 100644
index 0000000..98b2380
--- /dev/null
+++ b/azurelinuxagent/distro/default/handlerFactory.py
@@ -0,0 +1,40 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+from .init import InitHandler
+from .run import MainHandler
+from .scvmm import ScvmmHandler
+from .dhcp import DhcpHandler
+from .env import EnvHandler
+from .provision import ProvisionHandler
+from .resourceDisk import ResourceDiskHandler
+from .extension import ExtensionsHandler
+from .deprovision import DeprovisionHandler
+
+class DefaultHandlerFactory(object):
+ def __init__(self):
+ self.init_handler = InitHandler()
+ self.main_handler = MainHandler(self)
+ self.scvmm_handler = ScvmmHandler()
+ self.dhcp_handler = DhcpHandler()
+ self.env_handler = EnvHandler(self)
+ self.provision_handler = ProvisionHandler()
+ self.resource_disk_handler = ResourceDiskHandler()
+ self.extension_handler = ExtensionsHandler()
+ self.deprovision_handler = DeprovisionHandler()
+
diff --git a/azurelinuxagent/distro/default/init.py b/azurelinuxagent/distro/default/init.py
new file mode 100644
index 0000000..337fdea
--- /dev/null
+++ b/azurelinuxagent/distro/default/init.py
@@ -0,0 +1,49 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import os
+import azurelinuxagent.conf as conf
+import azurelinuxagent.logger as logger
+from azurelinuxagent.utils.osutil import OSUTIL
+import azurelinuxagent.utils.fileutil as fileutil
+
+
+class InitHandler(object):
+ def init(self, verbose):
+ #Init stdout log
+ level = logger.LogLevel.VERBOSE if verbose else logger.LogLevel.INFO
+ logger.add_logger_appender(logger.AppenderType.STDOUT, level)
+
+ #Init config
+ conf_file_path = OSUTIL.get_conf_file_path()
+ conf.load_conf(conf_file_path)
+
+ #Init log
+ verbose = verbose or conf.get_switch("Logs.Verbose", False)
+ level = logger.LogLevel.VERBOSE if verbose else logger.LogLevel.INFO
+ logger.add_logger_appender(logger.AppenderType.FILE, level,
+ path="/var/log/waagent.log")
+ logger.add_logger_appender(logger.AppenderType.CONSOLE, level,
+ path="/dev/console")
+
+ #Create lib dir
+ fileutil.mkdir(OSUTIL.get_lib_dir(), mode=0o700)
+ os.chdir(OSUTIL.get_lib_dir())
+
+
diff --git a/azurelinuxagent/distro/default/loader.py b/azurelinuxagent/distro/default/loader.py
new file mode 100644
index 0000000..d7dbe87
--- /dev/null
+++ b/azurelinuxagent/distro/default/loader.py
@@ -0,0 +1,28 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+def get_osutil():
+ from azurelinuxagent.distro.default.osutil import DefaultOSUtil
+ return DefaultOSUtil()
+
+def get_handlers():
+ from azurelinuxagent.distro.default.handlerFactory import DefaultHandlerFactory
+ return DefaultHandlerFactory()
+
+
diff --git a/azurelinuxagent/distro/default/osutil.py b/azurelinuxagent/distro/default/osutil.py
new file mode 100644
index 0000000..8e3fb77
--- /dev/null
+++ b/azurelinuxagent/distro/default/osutil.py
@@ -0,0 +1,657 @@
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import os
+import re
+import shutil
+import socket
+import array
+import struct
+import time
+import pwd
+import fcntl
+import azurelinuxagent.logger as logger
+from azurelinuxagent.future import text
+import azurelinuxagent.utils.fileutil as fileutil
+import azurelinuxagent.utils.shellutil as shellutil
+import azurelinuxagent.utils.textutil as textutil
+
+__RULES_FILES__ = [ "/lib/udev/rules.d/75-persistent-net-generator.rules",
+ "/etc/udev/rules.d/70-persistent-net.rules" ]
+
+"""
+Define distro specific behavior. OSUtil class defines default behavior
+for all distros. Each concrete distro classes could overwrite default behavior
+if needed.
+"""
+
+class OSUtilError(Exception):
+ pass
+
+class DefaultOSUtil(object):
+
+ def __init__(self):
+ self.lib_dir = "/var/lib/waagent"
+ self.ext_log_dir = "/var/log/azure"
+ self.dvd_mount_point = "/mnt/cdrom/secure"
+ self.ovf_env_file_path = "/mnt/cdrom/secure/ovf-env.xml"
+ self.agent_pid_file_path = "/var/run/waagent.pid"
+ self.passwd_file_path = "/etc/shadow"
+ self.home = '/home'
+ self.sshd_conf_file_path = '/etc/ssh/sshd_config'
+ self.openssl_cmd = '/usr/bin/openssl'
+ self.conf_file_path = '/etc/waagent.conf'
+ self.selinux=None
+
+ def get_lib_dir(self):
+ return self.lib_dir
+
+ def get_ext_log_dir(self):
+ return self.ext_log_dir
+
+ def get_dvd_mount_point(self):
+ return self.dvd_mount_point
+
+ def get_conf_file_path(self):
+ return self.conf_file_path
+
+ def get_ovf_env_file_path_on_dvd(self):
+ return self.ovf_env_file_path
+
+ def get_agent_pid_file_path(self):
+ return self.agent_pid_file_path
+
+ def get_openssl_cmd(self):
+ return self.openssl_cmd
+
+ def get_userentry(self, username):
+ try:
+ return pwd.getpwnam(username)
+ except KeyError:
+ return None
+
+ def is_sys_user(self, username):
+ userentry = self.get_userentry(username)
+ uidmin = None
+ try:
+ uidmin_def = fileutil.get_line_startingwith("UID_MIN",
+ "/etc/login.defs")
+ if uidmin_def is not None:
+ uidmin = int(uidmin_def.split()[1])
+ except IOError as e:
+ pass
+ if uidmin == None:
+ uidmin = 100
+ if userentry != None and userentry[2] < uidmin:
+ return True
+ else:
+ return False
+
+ def useradd(self, username, expiration=None):
+ """
+ Update password and ssh key for user account.
+ New account will be created if not exists.
+ """
+ if expiration is not None:
+ cmd = "useradd -m {0} -e {1}".format(username, expiration)
+ else:
+ cmd = "useradd -m {0}".format(username)
+ retcode, out = shellutil.run_get_output(cmd)
+ if retcode != 0:
+ raise OSUtilError(("Failed to create user account:{0}, "
+ "retcode:{1}, "
+ "output:{2}").format(username, retcode, out))
+
+ def chpasswd(self, username, password, use_salt=True, salt_type=6,
+ salt_len=10):
+ if self.is_sys_user(username):
+ raise OSUtilError(("User {0} is a system user. "
+ "Will not set passwd.").format(username))
+ passwd_hash = textutil.gen_password_hash(password, use_salt, salt_type,
+ salt_len)
+ try:
+ passwd_content = fileutil.read_file(self.passwd_file_path)
+ passwd = passwd_content.split("\n")
+ new_passwd = [x for x in passwd if not x.startswith(username)]
+ new_passwd.append("{0}:{1}:14600::::::".format(username, passwd_hash))
+ fileutil.write_file(self.passwd_file_path, "\n".join(new_passwd))
+ except IOError as e:
+ raise OSUtilError(("Failed to set password for {0}: {1}"
+ "").format(username, e))
+
+ def conf_sudoer(self, username, nopasswd):
+ # for older distros create sudoers.d
+ if not os.path.isdir('/etc/sudoers.d/'):
+ # create the /etc/sudoers.d/ directory
+ os.mkdir('/etc/sudoers.d/')
+ # add the include of sudoers.d to the /etc/sudoers
+ sudoers = '\n' + '#includedir /etc/sudoers.d/\n'
+ fileutil.append_file('/etc/sudoers', sudoers)
+ sudoer = None
+ if nopasswd:
+ sudoer = "{0} ALL = (ALL) NOPASSWD\n".format(username)
+ else:
+ sudoer = "{0} ALL = (ALL) ALL\n".format(username)
+ fileutil.append_file('/etc/sudoers.d/waagent', sudoer)
+ fileutil.chmod('/etc/sudoers.d/waagent', 0o440)
+
+ def del_root_password(self):
+ try:
+ passwd_content = fileutil.read_file(self.passwd_file_path)
+ passwd = passwd_content.split('\n')
+ new_passwd = [x for x in passwd if not x.startswith("root:")]
+ new_passwd.insert(0, "root:*LOCK*:14600::::::")
+ fileutil.write_file(self.passwd_file_path, "\n".join(new_passwd))
+ except IOError as e:
+ raise OSUtilError("Failed to delete root password:{0}".format(e))
+
+ def get_home(self):
+ return self.home
+
+ def get_pubkey_from_prv(self, file_name):
+ cmd = "{0} rsa -in {1} -pubout 2>/dev/null".format(self.openssl_cmd,
+ file_name)
+ pub = shellutil.run_get_output(cmd)[1]
+ return pub
+
+ def get_pubkey_from_crt(self, file_name):
+ cmd = "{0} x509 -in {1} -pubkey -noout".format(self.openssl_cmd,
+ file_name)
+ pub = shellutil.run_get_output(cmd)[1]
+ return pub
+
+ def _norm_path(self, filepath):
+ home = self.get_home()
+ # Expand HOME variable if present in path
+ path = os.path.normpath(filepath.replace("$HOME", home))
+ return path
+
+ def get_thumbprint_from_crt(self, file_name):
+ cmd="{0} x509 -in {1} -fingerprint -noout".format(self.openssl_cmd,
+ file_name)
+ thumbprint = shellutil.run_get_output(cmd)[1]
+ thumbprint = thumbprint.rstrip().split('=')[1].replace(':', '').upper()
+ return thumbprint
+
+ def deploy_ssh_keypair(self, username, keypair):
+ """
+ Deploy id_rsa and id_rsa.pub
+ """
+ path, thumbprint = keypair
+ path = self._norm_path(path)
+ dir_path = os.path.dirname(path)
+ fileutil.mkdir(dir_path, mode=0o700, owner=username)
+ lib_dir = self.get_lib_dir()
+ prv_path = os.path.join(lib_dir, thumbprint + '.prv')
+ if not os.path.isfile(prv_path):
+ raise OSUtilError("Can't find {0}.prv".format(thumbprint))
+ shutil.copyfile(prv_path, path)
+ pub_path = path + '.pub'
+ pub = self.get_pubkey_from_prv(prv_path)
+ fileutil.write_file(pub_path, pub)
+ self.set_selinux_context(pub_path, 'unconfined_u:object_r:ssh_home_t:s0')
+ self.set_selinux_context(path, 'unconfined_u:object_r:ssh_home_t:s0')
+ os.chmod(path, 0o644)
+ os.chmod(pub_path, 0o600)
+
+ def openssl_to_openssh(self, input_file, output_file):
+ shellutil.run("ssh-keygen -i -m PKCS8 -f {0} >> {1}".format(input_file,
+ output_file))
+
+ def deploy_ssh_pubkey(self, username, pubkey):
+ """
+ Deploy authorized_key
+ """
+ path, thumbprint, value = pubkey
+ if path is None:
+ raise OSUtilError("Publich key path is None")
+
+ path = self._norm_path(path)
+ dir_path = os.path.dirname(path)
+ fileutil.mkdir(dir_path, mode=0o700, owner=username)
+ if value is not None:
+ if not value.startswith("ssh-"):
+ raise OSUtilError("Bad public key: {0}".format(value))
+ fileutil.write_file(path, value)
+ elif thumbprint is not None:
+ lib_dir = self.get_lib_dir()
+ crt_path = os.path.join(lib_dir, thumbprint + '.crt')
+ if not os.path.isfile(crt_path):
+ raise OSUtilError("Can't find {0}.crt".format(thumbprint))
+ pub_path = os.path.join(lib_dir, thumbprint + '.pub')
+ pub = self.get_pubkey_from_crt(crt_path)
+ fileutil.write_file(pub_path, pub)
+ self.set_selinux_context(pub_path,
+ 'unconfined_u:object_r:ssh_home_t:s0')
+ self.openssl_to_openssh(pub_path, path)
+ fileutil.chmod(pub_path, 0o600)
+ else:
+ raise OSUtilError("SSH public key Fingerprint and Value are None")
+
+ self.set_selinux_context(path, 'unconfined_u:object_r:ssh_home_t:s0')
+ fileutil.chowner(path, username)
+ fileutil.chmod(path, 0o644)
+
+ def is_selinux_system(self):
+ """
+ Checks and sets self.selinux = True if SELinux is available on system.
+ """
+ if self.selinux == None:
+ if shellutil.run("which getenforce", chk_err=False) == 0:
+ self.selinux = True
+ else:
+ self.selinux = False
+ return self.selinux
+
+ def is_selinux_enforcing(self):
+ """
+ Calls shell command 'getenforce' and returns True if 'Enforcing'.
+ """
+ if self.is_selinux_system():
+ output = shellutil.run_get_output("getenforce")[1]
+ return output.startswith("Enforcing")
+ else:
+ return False
+
+ def set_selinux_enforce(self, state):
+ """
+ Calls shell command 'setenforce' with 'state'
+ and returns resulting exit code.
+ """
+ if self.is_selinux_system():
+ if state: s = '1'
+ else: s='0'
+ return shellutil.run("setenforce "+s)
+
+ def set_selinux_context(self, path, con):
+ """
+ Calls shell 'chcon' with 'path' and 'con' context.
+ Returns exit result.
+ """
+ if self.is_selinux_system():
+ return shellutil.run('chcon ' + con + ' ' + path)
+
+ def get_sshd_conf_file_path(self):
+ return self.sshd_conf_file_path
+
+ def set_ssh_client_alive_interval(self):
+ conf_file_path = self.get_sshd_conf_file_path()
+ conf = fileutil.read_file(conf_file_path).split("\n")
+ textutil.set_ssh_config(conf, "ClientAliveInterval", "180")
+ fileutil.write_file(conf_file_path, '\n'.join(conf))
+ logger.info("Configured SSH client probing to keep connections alive.")
+
+ def conf_sshd(self, disable_password):
+ option = "no" if disable_password else "yes"
+ conf_file_path = self.get_sshd_conf_file_path()
+ conf = fileutil.read_file(conf_file_path).split("\n")
+ textutil.set_ssh_config(conf, "PasswordAuthentication", option)
+ textutil.set_ssh_config(conf, "ChallengeResponseAuthentication", option)
+ fileutil.write_file(conf_file_path, "\n".join(conf))
+ logger.info("Disabled SSH password-based authentication methods.")
+
+
+ def get_dvd_device(self, dev_dir='/dev'):
+ patten=r'(sr[0-9]|hd[c-z]|cdrom[0-9])'
+ for dvd in [re.match(patten, dev) for dev in os.listdir(dev_dir)]:
+ if dvd is not None:
+ return "/dev/{0}".format(dvd.group(0))
+ raise OSUtilError("Failed to get dvd device")
+
+ def mount_dvd(self, max_retry=6, chk_err=True):
+ dvd = self.get_dvd_device()
+ mount_point = self.get_dvd_mount_point()
+ mountlist = shellutil.run_get_output("mount")[1]
+ existing = self.get_mount_point(mountlist, dvd)
+ if existing is not None: #Already mounted
+ logger.info("{0} is already mounted at {1}", dvd, existing)
+ return
+ if not os.path.isdir(mount_point):
+ os.makedirs(mount_point)
+
+ for retry in range(0, max_retry):
+ retcode = self.mount(dvd, mount_point, option="-o ro -t iso9660,udf",
+ chk_err=chk_err)
+ if retcode == 0:
+ logger.info("Successfully mounted dvd")
+ return
+ if retry < max_retry - 1:
+ logger.warn("Mount dvd failed: retry={0}, ret={1}", retry,
+ retcode)
+ time.sleep(5)
+ if chk_err:
+ raise OSUtilError("Failed to mount dvd.")
+
+ def umount_dvd(self, chk_err=True):
+ mount_point = self.get_dvd_mount_point()
+ retcode = self.umount(mount_point, chk_err=chk_err)
+ if chk_err and retcode != 0:
+ raise OSUtilError("Failed to umount dvd.")
+
+ def eject_dvd(self, chk_err=True):
+ retcode = shellutil.run("eject")
+ if chk_err and retcode != 0:
+ raise OSUtilError("Failed to eject dvd")
+
+ def load_atappix_mod(self):
+ if self.is_atapiix_mod_loaded():
+ return
+ ret, kern_version = shellutil.run_get_output("uname -r")
+ if ret != 0:
+ raise Exception("Failed to call uname -r")
+ mod_path = os.path.join('/lib/modules',
+ kern_version.strip('\n'),
+ 'kernel/drivers/ata/ata_piix.ko')
+ if not os.path.isfile(mod_path):
+ raise Exception("Can't find module file:{0}".format(mod_path))
+
+ ret, output = shellutil.run_get_output("insmod " + mod_path)
+ if ret != 0:
+ raise Exception("Error calling insmod for ATAPI CD-ROM driver")
+ if not self.is_atapiix_mod_loaded(max_retry=3):
+ raise Exception("Failed to load ATAPI CD-ROM driver")
+
+ def is_atapiix_mod_loaded(self, max_retry=1):
+ for retry in range(0, max_retry):
+ ret = shellutil.run("lsmod | grep ata_piix", chk_err=False)
+ if ret == 0:
+ logger.info("Module driver for ATAPI CD-ROM is already present.")
+ return True
+ if retry < max_retry - 1:
+ time.sleep(1)
+ return False
+
+ def mount(self, dvd, mount_point, option="", chk_err=True):
+ cmd = "mount {0} {1} {2}".format(dvd, option, mount_point)
+ return shellutil.run_get_output(cmd, chk_err)[0]
+
+ def umount(self, mount_point, chk_err=True):
+ return shellutil.run("umount {0}".format(mount_point), chk_err=chk_err)
+
+ def allow_dhcp_broadcast(self):
+ #Open DHCP port if iptables is enabled.
+ # We supress error logging on error.
+ shellutil.run("iptables -D INPUT -p udp --dport 68 -j ACCEPT",
+ chk_err=False)
+ shellutil.run("iptables -I INPUT -p udp --dport 68 -j ACCEPT",
+ chk_err=False)
+
+ def gen_transport_cert(self):
+ """
+ Create ssl certificate for https communication with endpoint server.
+ """
+ cmd = ("{0} req -x509 -nodes -subj /CN=LinuxTransport -days 32768 "
+ "-newkey rsa:2048 -keyout TransportPrivate.pem "
+ "-out TransportCert.pem").format(self.openssl_cmd)
+ shellutil.run(cmd)
+
+ def remove_rules_files(self, rules_files=__RULES_FILES__):
+ lib_dir = self.get_lib_dir()
+ for src in rules_files:
+ file_name = fileutil.base_name(src)
+ dest = os.path.join(lib_dir, file_name)
+ if os.path.isfile(dest):
+ os.remove(dest)
+ if os.path.isfile(src):
+ logger.warn("Move rules file {0} to {1}", file_name, dest)
+ shutil.move(src, dest)
+
+ def restore_rules_files(self, rules_files=__RULES_FILES__):
+ lib_dir = self.get_lib_dir()
+ for dest in rules_files:
+ filename = fileutil.base_name(dest)
+ src = os.path.join(lib_dir, filename)
+ if os.path.isfile(dest):
+ continue
+ if os.path.isfile(src):
+ logger.warn("Move rules file {0} to {1}", filename, dest)
+ shutil.move(src, dest)
+
+ def get_mac_addr(self):
+ """
+ Convienience function, returns mac addr bound to
+ first non-loobback interface.
+ """
+ ifname=''
+ while len(ifname) < 2 :
+ ifname=self.get_first_if()[0]
+ addr = self.get_if_mac(ifname)
+ return textutil.hexstr_to_bytearray(addr)
+
+ def get_if_mac(self, ifname):
+ """
+ Return the mac-address bound to the socket.
+ """
+ sock = socket.socket(socket.AF_INET,
+ socket.SOCK_DGRAM,
+ socket.IPPROTO_UDP)
+ param = struct.pack('256s', (ifname[:15]+('\0'*241)).encode('latin-1'))
+ info = fcntl.ioctl(sock.fileno(), 0x8927, param)
+ return ''.join(['%02X' % textutil.str_to_ord(char) for char in info[18:24]])
+
+ def get_first_if(self):
+ """
+ Return the interface name, and ip addr of the
+ first active non-loopback interface.
+ """
+ iface=''
+ expected=16 # how many devices should I expect...
+ struct_size=40 # for 64bit the size is 40 bytes
+ sock = socket.socket(socket.AF_INET,
+ socket.SOCK_DGRAM,
+ socket.IPPROTO_UDP)
+ buff=array.array('B', b'\0' * (expected * struct_size))
+ param = struct.pack('iL',
+ expected*struct_size,
+ buff.buffer_info()[0])
+ ret = fcntl.ioctl(sock.fileno(), 0x8912, param)
+ retsize=(struct.unpack('iL', ret)[0])
+ if retsize == (expected * struct_size):
+ logger.warn(('SIOCGIFCONF returned more than {0} up '
+ 'network interfaces.'), expected)
+ sock = buff.tostring()
+ for i in range(0, struct_size * expected, struct_size):
+ iface=sock[i:i+16].split(b'\0', 1)[0]
+ if iface == b'lo':
+ continue
+ else:
+ break
+ return iface.decode('latin-1'), socket.inet_ntoa(sock[i+20:i+24])
+
+ def is_missing_default_route(self):
+ routes = shellutil.run_get_output("route -n")[1]
+ for route in routes.split("\n"):
+ if route.startswith("0.0.0.0 ") or route.startswith("default "):
+ return False
+ return True
+
+ def get_if_name(self):
+ return self.get_first_if()[0]
+
+ def get_ip4_addr(self):
+ return self.get_first_if()[1]
+
+ def set_route_for_dhcp_broadcast(self, ifname):
+ return shellutil.run("route add 255.255.255.255 dev {0}".format(ifname),
+ chk_err=False)
+
+ def remove_route_for_dhcp_broadcast(self, ifname):
+ shellutil.run("route del 255.255.255.255 dev {0}".format(ifname),
+ chk_err=False)
+
+ def is_dhcp_enabled(self):
+ return False
+
+ def stop_dhcp_service(self):
+ pass
+
+ def start_dhcp_service(self):
+ pass
+
+ def start_network(self):
+ pass
+
+ def start_agent_service(self):
+ pass
+
+ def stop_agent_service(self):
+ pass
+
+ def register_agent_service(self):
+ pass
+
+ def unregister_agent_service(self):
+ pass
+
+ def restart_ssh_service(self):
+ pass
+
+ def route_add(self, net, mask, gateway):
+ """
+ Add specified route using /sbin/route add -net.
+ """
+ cmd = ("/sbin/route add -net "
+ "{0} netmask {1} gw {2}").format(net, mask, gateway)
+ return shellutil.run(cmd, chk_err=False)
+
+ def get_dhcp_pid(self):
+ ret= shellutil.run_get_output("pidof dhclient")
+ return ret[1] if ret[0] == 0 else None
+
+ def set_hostname(self, hostname):
+ fileutil.write_file('/etc/hostname', hostname)
+ shellutil.run("hostname {0}".format(hostname), chk_err=False)
+
+ def set_dhcp_hostname(self, hostname):
+ autosend = r'^[^#]*?send\s*host-name.*?(<hostname>|gethostname[(,)])'
+ dhclient_files = ['/etc/dhcp/dhclient.conf', '/etc/dhcp3/dhclient.conf']
+ for conf_file in dhclient_files:
+ if not os.path.isfile(conf_file):
+ continue
+ if fileutil.findstr_in_file(conf_file, autosend):
+ #Return if auto send host-name is configured
+ return
+ fileutil.update_conf_file(conf_file,
+ 'send host-name',
+ 'send host-name {0}'.format(hostname))
+
+ def restart_if(self, ifname):
+ shellutil.run("ifdown {0} && ifup {1}".format(ifname, ifname))
+
+ def publish_hostname(self, hostname):
+ self.set_dhcp_hostname(hostname)
+ ifname = self.get_if_name()
+ self.restart_if(ifname)
+
+ def set_scsi_disks_timeout(self, timeout):
+ for dev in os.listdir("/sys/block"):
+ if dev.startswith('sd'):
+ self.set_block_device_timeout(dev, timeout)
+
+ def set_block_device_timeout(self, dev, timeout):
+ if dev is not None and timeout is not None:
+ file_path = "/sys/block/{0}/device/timeout".format(dev)
+ content = fileutil.read_file(file_path)
+ original = content.splitlines()[0].rstrip()
+ if original != timeout:
+ fileutil.write_file(file_path, timeout)
+ logger.info("Set block dev timeout: {0} with timeout: {1}",
+ dev, timeout)
+
+ def get_mount_point(self, mountlist, device):
+ """
+ Example of mountlist:
+ /dev/sda1 on / type ext4 (rw)
+ proc on /proc type proc (rw)
+ sysfs on /sys type sysfs (rw)
+ devpts on /dev/pts type devpts (rw,gid=5,mode=620)
+ tmpfs on /dev/shm type tmpfs
+ (rw,rootcontext="system_u:object_r:tmpfs_t:s0")
+ none on /proc/sys/fs/binfmt_misc type binfmt_misc (rw)
+ /dev/sdb1 on /mnt/resource type ext4 (rw)
+ """
+ if (mountlist and device):
+ for entry in mountlist.split('\n'):
+ if(re.search(device, entry)):
+ tokens = entry.split()
+ #Return the 3rd column of this line
+ return tokens[2] if len(tokens) > 2 else None
+ return None
+
+ def device_for_ide_port(self, port_id):
+ """
+ Return device name attached to ide port 'n'.
+ """
+ if port_id > 3:
+ return None
+ g0 = "00000000"
+ if port_id > 1:
+ g0 = "00000001"
+ port_id = port_id - 2
+ device = None
+ path = "/sys/bus/vmbus/devices/"
+ for vmbus in os.listdir(path):
+ deviceid = fileutil.read_file(os.path.join(path, vmbus, "device_id"))
+ guid = deviceid.lstrip('{').split('-')
+ if guid[0] == g0 and guid[1] == "000" + text(port_id):
+ for root, dirs, files in os.walk(path + vmbus):
+ if root.endswith("/block"):
+ device = dirs[0]
+ break
+ else : #older distros
+ for d in dirs:
+ if ':' in d and "block" == d.split(':')[0]:
+ device = d.split(':')[1]
+ break
+ break
+ return device
+
+ def del_account(self, username):
+ if self.is_sys_user(username):
+ logger.error("{0} is a system user. Will not delete it.", username)
+ shellutil.run("> /var/run/utmp")
+ shellutil.run("userdel -f -r " + username)
+ #Remove user from suders
+ if os.path.isfile("/etc/suders.d/waagent"):
+ try:
+ content = fileutil.read_file("/etc/sudoers.d/waagent")
+ sudoers = content.split("\n")
+ sudoers = [x for x in sudoers if username not in x]
+ fileutil.write_file("/etc/sudoers.d/waagent",
+ "\n".join(sudoers))
+ except IOError as e:
+ raise OSUtilError("Failed to remove sudoer: {0}".format(e))
+
+ def decode_customdata(self, data):
+ return data
+
+ def get_total_mem(self):
+ cmd = "grep MemTotal /proc/meminfo |awk '{print $2}'"
+ ret = shellutil.run_get_output(cmd)
+ if ret[0] == 0:
+ return int(ret[1])/1024
+ else:
+ raise OSUtilError("Failed to get total memory: {0}".format(ret[1]))
+
+ def get_processor_cores(self):
+ ret = shellutil.run_get_output("grep 'processor.*:' /proc/cpuinfo |wc -l")
+ if ret[0] == 0:
+ return int(ret[1])
+ else:
+ raise OSUtilError("Failed to get procerssor cores")
+
diff --git a/azurelinuxagent/distro/default/provision.py b/azurelinuxagent/distro/default/provision.py
new file mode 100644
index 0000000..1e9c459
--- /dev/null
+++ b/azurelinuxagent/distro/default/provision.py
@@ -0,0 +1,165 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+"""
+Provision handler
+"""
+
+import os
+import azurelinuxagent.logger as logger
+from azurelinuxagent.future import text
+import azurelinuxagent.conf as conf
+from azurelinuxagent.event import add_event, WALAEventOperation
+from azurelinuxagent.exception import *
+from azurelinuxagent.utils.osutil import OSUTIL, OSUtilError
+import azurelinuxagent.protocol as prot
+import azurelinuxagent.protocol.ovfenv as ovf
+import azurelinuxagent.utils.shellutil as shellutil
+import azurelinuxagent.utils.fileutil as fileutil
+
+CUSTOM_DATA_FILE="CustomData"
+
+class ProvisionHandler(object):
+
+ def process(self):
+ #If provision is not enabled, return
+ if not conf.get_switch("Provisioning.Enabled", True):
+ logger.info("Provisioning is disabled. Skip.")
+ return
+
+ provisioned = os.path.join(OSUTIL.get_lib_dir(), "provisioned")
+ if os.path.isfile(provisioned):
+ return
+
+ logger.info("run provision handler.")
+ protocol = prot.FACTORY.get_default_protocol()
+ try:
+ status = prot.ProvisionStatus(status="NotReady",
+ subStatus="Provision started")
+ protocol.report_provision_status(status)
+
+ self.provision()
+ fileutil.write_file(provisioned, "")
+ thumbprint = self.reg_ssh_host_key()
+
+ logger.info("Finished provisioning")
+ status = prot.ProvisionStatus(status="Ready")
+ status.properties.certificateThumbprint = thumbprint
+ protocol.report_provision_status(status)
+
+ add_event(name="WALA", is_success=True, message="",
+ op=WALAEventOperation.Provision)
+ except ProvisionError as e:
+ logger.error("Provision failed: {0}", e)
+ status = prot.ProvisionStatus(status="NotReady",
+ subStatus= text(e))
+ protocol.report_provision_status(status)
+ add_event(name="WALA", is_success=False, message=text(e),
+ op=WALAEventOperation.Provision)
+
+ def reg_ssh_host_key(self):
+ keypair_type = conf.get("Provisioning.SshHostKeyPairType", "rsa")
+ if conf.get_switch("Provisioning.RegenerateSshHostKeyPair"):
+ shellutil.run("rm -f /etc/ssh/ssh_host_*key*")
+ shellutil.run(("ssh-keygen -N '' -t {0} -f /etc/ssh/ssh_host_{1}_key"
+ "").format(keypair_type, keypair_type))
+ thumbprint = self.get_ssh_host_key_thumbprint(keypair_type)
+ return thumbprint
+
+ def get_ssh_host_key_thumbprint(self, keypair_type):
+ cmd = "ssh-keygen -lf /etc/ssh/ssh_host_{0}_key.pub".format(keypair_type)
+ ret = shellutil.run_get_output(cmd)
+ if ret[0] == 0:
+ return ret[1].rstrip().split()[1].replace(':', '')
+ else:
+ raise ProvisionError(("Failed to generate ssh host key: "
+ "ret={0}, out= {1}").format(ret[0], ret[1]))
+
+
+ def provision(self):
+ logger.info("Copy ovf-env.xml.")
+ try:
+ ovfenv = ovf.copy_ovf_env()
+ except prot.ProtocolError as e:
+ raise ProvisionError("Failed to copy ovf-env.xml: {0}".format(e))
+
+ logger.info("Handle ovf-env.xml.")
+ try:
+ logger.info("Set host name.")
+ OSUTIL.set_hostname(ovfenv.hostname)
+
+ logger.info("Publish host name.")
+ OSUTIL.publish_hostname(ovfenv.hostname)
+
+ self.config_user_account(ovfenv)
+
+ self.save_customdata(ovfenv)
+
+ if conf.get_switch("Provisioning.DeleteRootPassword"):
+ OSUTIL.del_root_password()
+ except OSUtilError as e:
+ raise ProvisionError("Failed to handle ovf-env.xml: {0}".format(e))
+
+ def config_user_account(self, ovfenv):
+ logger.info("Create user account if not exists")
+ OSUTIL.useradd(ovfenv.username)
+
+ if ovfenv.user_password is not None:
+ logger.info("Set user password.")
+ use_salt = conf.get_switch("Provision.UseSalt", True)
+ salt_type = conf.get_switch("Provision.SaltType", 6)
+ OSUTIL.chpasswd(ovfenv.username, ovfenv.user_password,
+ use_salt,salt_type)
+
+ logger.info("Configure sudoer")
+ OSUTIL.conf_sudoer(ovfenv.username, ovfenv.user_password is None)
+
+ logger.info("Configure sshd")
+ OSUTIL.conf_sshd(ovfenv.disable_ssh_password_auth)
+
+ #Disable selinux temporary
+ sel = OSUTIL.is_selinux_enforcing()
+ if sel:
+ OSUTIL.set_selinux_enforce(0)
+
+ self.deploy_ssh_pubkeys(ovfenv)
+ self.deploy_ssh_keypairs(ovfenv)
+
+ if sel:
+ OSUTIL.set_selinux_enforce(1)
+
+ OSUTIL.restart_ssh_service()
+
+ def save_customdata(self, ovfenv):
+ logger.info("Save custom data")
+ customdata = ovfenv.customdata
+ if customdata is None:
+ return
+ lib_dir = OSUTIL.get_lib_dir()
+ fileutil.write_file(os.path.join(lib_dir, CUSTOM_DATA_FILE),
+ OSUTIL.decode_customdata(customdata))
+
+ def deploy_ssh_pubkeys(self, ovfenv):
+ for pubkey in ovfenv.ssh_pubkeys:
+ logger.info("Deploy ssh public key.")
+ OSUTIL.deploy_ssh_pubkey(ovfenv.username, pubkey)
+
+ def deploy_ssh_keypairs(self, ovfenv):
+ for keypair in ovfenv.ssh_keypairs:
+ logger.info("Deploy ssh key pairs.")
+ OSUTIL.deploy_ssh_keypair(ovfenv.username, keypair)
+
diff --git a/azurelinuxagent/distro/default/resourceDisk.py b/azurelinuxagent/distro/default/resourceDisk.py
new file mode 100644
index 0000000..d4ef1c9
--- /dev/null
+++ b/azurelinuxagent/distro/default/resourceDisk.py
@@ -0,0 +1,166 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import os
+import re
+import threading
+import azurelinuxagent.logger as logger
+from azurelinuxagent.future import text
+import azurelinuxagent.conf as conf
+from azurelinuxagent.utils.osutil import OSUTIL
+from azurelinuxagent.event import add_event, WALAEventOperation
+import azurelinuxagent.utils.fileutil as fileutil
+import azurelinuxagent.utils.shellutil as shellutil
+from azurelinuxagent.exception import ResourceDiskError
+
+DATALOSS_WARNING_FILE_NAME="DATALOSS_WARNING_README.txt"
+DATA_LOSS_WARNING="""\
+WARNING: THIS IS A TEMPORARY DISK.
+
+Any data stored on this drive is SUBJECT TO LOSS and THERE IS NO WAY TO RECOVER IT.
+
+Please do not use this disk for storing any personal or application data.
+
+For additional details to please refer to the MSDN documentation at : http://msdn.microsoft.com/en-us/library/windowsazure/jj672979.aspx
+"""
+
+class ResourceDiskHandler(object):
+
+ def start_activate_resource_disk(self):
+ disk_thread = threading.Thread(target = self.run)
+ disk_thread.start()
+
+ def run(self):
+ mount_point = None
+ if conf.get_switch("ResourceDisk.Format", False):
+ mount_point = self.activate_resource_disk()
+ if mount_point is not None and \
+ conf.get_switch("ResourceDisk.EnableSwap", False):
+ self.enable_swap(mount_point)
+
+ def activate_resource_disk(self):
+ logger.info("Activate resource disk")
+ try:
+ mount_point = conf.get("ResourceDisk.MountPoint", "/mnt/resource")
+ fs = conf.get("ResourceDisk.Filesystem", "ext3")
+ mount_point = self.mount_resource_disk(mount_point, fs)
+ warning_file = os.path.join(mount_point, DATALOSS_WARNING_FILE_NAME)
+ try:
+ fileutil.write_file(warning_file, DATA_LOSS_WARNING)
+ except IOError as e:
+ logger.warn("Failed to write data loss warnning:{0}", e)
+ return mount_point
+ except ResourceDiskError as e:
+ logger.error("Failed to mount resource disk {0}", e)
+ add_event(name="WALA", is_success=False, message=text(e),
+ op=WALAEventOperation.ActivateResourceDisk)
+
+ def enable_swap(self, mount_point):
+ logger.info("Enable swap")
+ try:
+ size_mb = conf.get_int("ResourceDisk.SwapSizeMB", 0)
+ self.create_swap_space(mount_point, size_mb)
+ except ResourceDiskError as e:
+ logger.error("Failed to enable swap {0}", e)
+
+ def mount_resource_disk(self, mount_point, fs):
+ device = OSUTIL.device_for_ide_port(1)
+ if device is None:
+ raise ResourceDiskError("unable to detect disk topology")
+
+ device = "/dev/" + device
+ mountlist = shellutil.run_get_output("mount")[1]
+ existing = OSUTIL.get_mount_point(mountlist, device)
+
+ if(existing):
+ logger.info("Resource disk {0}1 is already mounted", device)
+ return existing
+
+ fileutil.mkdir(mount_point, mode=0o755)
+
+ logger.info("Detect GPT...")
+ partition = device + "1"
+ ret = shellutil.run_get_output("parted {0} print".format(device))
+ if ret[0]:
+ raise ResourceDiskError("({0}) {1}".format(device, ret[1]))
+
+ if "gpt" in ret[1]:
+ logger.info("GPT detected")
+ logger.info("Get GPT partitions")
+ parts = [x for x in ret[1].split("\n") if re.match("^\s*[0-9]+", x)]
+ logger.info("Found more than {0} GPT partitions.", len(parts))
+ if len(parts) > 1:
+ logger.info("Remove old GPT partitions")
+ for i in range(1, len(parts) + 1):
+ logger.info("Remove partition: {0}", i)
+ shellutil.run("parted {0} rm {1}".format(device, i))
+
+ logger.info("Create a new GPT partition using entire disk space")
+ shellutil.run("parted {0} mkpart primary 0% 100%".format(device))
+
+ logger.info("Format partition: {0} with fstype {1}",partition,fs)
+ shellutil.run("mkfs." + fs + " " + partition + " -F")
+ else:
+ logger.info("GPT not detected")
+ logger.info("Check fstype")
+ ret = shellutil.run_get_output("sfdisk -q -c {0} 1".format(device))
+ if ret[1].rstrip() == "7" and fs != "ntfs":
+ logger.info("The partition is formatted with ntfs")
+ logger.info("Format partition: {0} with fstype {1}",partition,fs)
+ shellutil.run("sfdisk -c {0} 1 83".format(device))
+ shellutil.run("mkfs." + fs + " " + partition + " -F")
+
+ logger.info("Mount resource disk")
+ ret = shellutil.run("mount {0} {1}".format(partition, mount_point),
+ chk_err=False)
+ if ret:
+ logger.warn("Failed to mount resource disk. Retry mounting")
+ shellutil.run("mkfs." + fs + " " + partition + " -F")
+ ret = shellutil.run("mount {0} {1}".format(partition, mount_point))
+ if ret:
+ raise ResourceDiskError("({0}) {1}".format(partition, ret))
+
+ logger.info("Resource disk ({0}) is mounted at {1} with fstype {2}",
+ device, mount_point, fs)
+ return mount_point
+
+ def create_swap_space(self, mount_point, size_mb):
+ size_kb = size_mb * 1024
+ size = size_kb * 1024
+ swapfile = os.path.join(mount_point, 'swapfile')
+ swaplist = shellutil.run_get_output("swapon -s")[1]
+
+ if swapfile in swaplist and os.path.getsize(swapfile) == size:
+ logger.info("Swap already enabled")
+ return
+
+ if os.path.isfile(swapfile) and os.path.getsize(swapfile) != size:
+ logger.info("Remove old swap file")
+ shellutil.run("swapoff -a", chk_err=False)
+ os.remove(swapfile)
+
+ if not os.path.isfile(swapfile):
+ logger.info("Create swap file")
+ shellutil.run(("dd if=/dev/zero of={0} bs=1024 "
+ "count={1}").format(swapfile, size_kb))
+ shellutil.run("mkswap {0}".format(swapfile))
+ if shellutil.run("swapon {0}".format(swapfile)):
+ raise ResourceDiskError("{0}".format(swapfile))
+ logger.info("Enabled {0}KB of swap at {1}".format(size_kb, swapfile))
+
diff --git a/azurelinuxagent/distro/default/run.py b/azurelinuxagent/distro/default/run.py
new file mode 100644
index 0000000..13880b4
--- /dev/null
+++ b/azurelinuxagent/distro/default/run.py
@@ -0,0 +1,86 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import os
+import time
+import sys
+import azurelinuxagent.logger as logger
+from azurelinuxagent.future import text
+import azurelinuxagent.conf as conf
+from azurelinuxagent.metadata import AGENT_LONG_NAME, AGENT_VERSION, \
+ DISTRO_NAME, DISTRO_VERSION, \
+ DISTRO_FULL_NAME, PY_VERSION_MAJOR, \
+ PY_VERSION_MINOR, PY_VERSION_MICRO
+import azurelinuxagent.protocol as prot
+import azurelinuxagent.event as event
+from azurelinuxagent.utils.osutil import OSUTIL
+import azurelinuxagent.utils.fileutil as fileutil
+
+
+class MainHandler(object):
+ def __init__(self, handlers):
+ self.handlers = handlers
+
+ def run(self):
+ logger.info("{0} Version:{1}", AGENT_LONG_NAME, AGENT_VERSION)
+ logger.info("OS: {0} {1}", DISTRO_NAME, DISTRO_VERSION)
+ logger.info("Python: {0}.{1}.{2}", PY_VERSION_MAJOR, PY_VERSION_MINOR,
+ PY_VERSION_MICRO)
+
+ event.enable_unhandled_err_dump("Azure Linux Agent")
+ fileutil.write_file(OSUTIL.get_agent_pid_file_path(), text(os.getpid()))
+
+ if conf.get_switch("DetectScvmmEnv", False):
+ if self.handlers.scvmm_handler.detect_scvmm_env():
+ return
+
+ self.handlers.dhcp_handler.probe()
+
+ prot.detect_default_protocol()
+
+ event.EventMonitor().start()
+
+ self.handlers.provision_handler.process()
+
+ if conf.get_switch("ResourceDisk.Format", False):
+ self.handlers.resource_disk_handler.start_activate_resource_disk()
+
+ self.handlers.env_handler.start()
+
+ protocol = prot.FACTORY.get_default_protocol()
+ while True:
+
+ #Handle extensions
+ h_status_list = self.handlers.extension_handler.process()
+
+ #Report status
+ vm_status = prot.VMStatus()
+ vm_status.vmAgent.agentVersion = AGENT_LONG_NAME
+ vm_status.vmAgent.status = "Ready"
+ vm_status.vmAgent.message = "Guest Agent is running"
+ for h_status in h_status_list:
+ vm_status.extensionHandlers.append(h_status)
+ try:
+ logger.info("Report vm status")
+ protocol.report_status(vm_status)
+ except prot.ProtocolError as e:
+ logger.error("Failed to report vm status: {0}", e)
+
+ time.sleep(25)
+
diff --git a/azurelinuxagent/distro/default/scvmm.py b/azurelinuxagent/distro/default/scvmm.py
new file mode 100644
index 0000000..18fad4b
--- /dev/null
+++ b/azurelinuxagent/distro/default/scvmm.py
@@ -0,0 +1,47 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import os
+import subprocess
+import azurelinuxagent.logger as logger
+from azurelinuxagent.utils.osutil import OSUTIL
+
+VMM_CONF_FILE_NAME = "linuxosconfiguration.xml"
+VMM_STARTUP_SCRIPT_NAME= "install"
+
+class ScvmmHandler(object):
+
+ def detect_scvmm_env(self):
+ logger.info("Detecting Microsoft System Center VMM Environment")
+ OSUTIL.mount_dvd(max_retry=1, chk_err=False)
+ mount_point = OSUTIL.get_dvd_mount_point()
+ found = os.path.isfile(os.path.join(mount_point, VMM_CONF_FILE_NAME))
+ if found:
+ self.start_scvmm_agent()
+ else:
+ OSUTIL.umount_dvd(chk_err=False)
+ return found
+
+ def start_scvmm_agent(self):
+ logger.info("Starting Microsoft System Center VMM Initialization "
+ "Process")
+ mount_point = OSUTIL.get_dvd_mount_point()
+ startup_script = os.path.join(mount_point, VMM_STARTUP_SCRIPT_NAME)
+ subprocess.Popen(["/bin/bash", startup_script, "-p " + mount_point])
+
diff --git a/azurelinuxagent/distro/loader.py b/azurelinuxagent/distro/loader.py
new file mode 100644
index 0000000..0060a7f
--- /dev/null
+++ b/azurelinuxagent/distro/loader.py
@@ -0,0 +1,46 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import azurelinuxagent.logger as logger
+from azurelinuxagent.metadata import DISTRO_NAME
+import azurelinuxagent.distro.default.loader as default_loader
+
+
+def get_distro_loader():
+ try:
+ logger.verb("Loading distro implemetation from: {0}", DISTRO_NAME)
+ pkg_name = "azurelinuxagent.distro.{0}.loader".format(DISTRO_NAME)
+ return __import__(pkg_name, fromlist="loader")
+ except ImportError as e:
+ logger.warn("Unable to load distro implemetation for {0}.", DISTRO_NAME)
+ logger.warn("Use default distro implemetation instead.")
+ return default_loader
+
+DISTRO_LOADER = get_distro_loader()
+
+def get_osutil():
+ try:
+ return DISTRO_LOADER.get_osutil()
+ except AttributeError:
+ return default_loader.get_osutil()
+
+def get_handlers():
+ try:
+ return DISTRO_LOADER.get_handlers()
+ except AttributeError:
+ return default_loader.get_handlers()
+
diff --git a/azurelinuxagent/distro/oracle/__init__.py b/azurelinuxagent/distro/oracle/__init__.py
new file mode 100644
index 0000000..4b2b9e1
--- /dev/null
+++ b/azurelinuxagent/distro/oracle/__init__.py
@@ -0,0 +1,19 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
diff --git a/azurelinuxagent/distro/oracle/loader.py b/azurelinuxagent/distro/oracle/loader.py
new file mode 100644
index 0000000..379f027
--- /dev/null
+++ b/azurelinuxagent/distro/oracle/loader.py
@@ -0,0 +1,25 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+from azurelinuxagent.metadata import DISTRO_NAME, DISTRO_VERSION
+import azurelinuxagent.distro.redhat.loader as redhat
+
+def get_osutil():
+ return redhat.get_osutil()
+
diff --git a/azurelinuxagent/distro/redhat/__init__.py b/azurelinuxagent/distro/redhat/__init__.py
new file mode 100644
index 0000000..4b2b9e1
--- /dev/null
+++ b/azurelinuxagent/distro/redhat/__init__.py
@@ -0,0 +1,19 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
diff --git a/azurelinuxagent/distro/redhat/loader.py b/azurelinuxagent/distro/redhat/loader.py
new file mode 100644
index 0000000..911e74d
--- /dev/null
+++ b/azurelinuxagent/distro/redhat/loader.py
@@ -0,0 +1,28 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+from azurelinuxagent.metadata import DISTRO_NAME, DISTRO_VERSION
+
+def get_osutil():
+ from azurelinuxagent.distro.redhat.osutil import Redhat6xOSUtil, RedhatOSUtil
+ if DISTRO_VERSION < "7":
+ return Redhat6xOSUtil()
+ else:
+ return RedhatOSUtil()
+
diff --git a/azurelinuxagent/distro/redhat/osutil.py b/azurelinuxagent/distro/redhat/osutil.py
new file mode 100644
index 0000000..c6c3016
--- /dev/null
+++ b/azurelinuxagent/distro/redhat/osutil.py
@@ -0,0 +1,148 @@
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import os
+import re
+import pwd
+import shutil
+import socket
+import array
+import struct
+import fcntl
+import time
+import base64
+import azurelinuxagent.logger as logger
+from azurelinuxagent.future import text, bytebuffer
+import azurelinuxagent.utils.fileutil as fileutil
+import azurelinuxagent.utils.shellutil as shellutil
+import azurelinuxagent.utils.textutil as textutil
+from azurelinuxagent.distro.default.osutil import DefaultOSUtil, OSUtilError
+
+class Redhat6xOSUtil(DefaultOSUtil):
+ def __init__(self):
+ super(Redhat6xOSUtil, self).__init__()
+ self.sshd_conf_file_path = '/etc/ssh/sshd_config'
+ self.openssl_cmd = '/usr/bin/openssl'
+ self.conf_file_path = '/etc/waagent.conf'
+ self.selinux=None
+
+ def start_network(self):
+ return shellutil.run("/sbin/service networking start", chk_err=False)
+
+ def restart_ssh_service(self):
+ return shellutil.run("/sbin/service sshd condrestart", chk_err=False)
+
+ def stop_agent_service(self):
+ return shellutil.run("/sbin/service waagent stop", chk_err=False)
+
+ def start_agent_service(self):
+ return shellutil.run("/sbin/service waagent start", chk_err=False)
+
+ def register_agent_service(self):
+ return shellutil.run("chkconfig --add waagent", chk_err=False)
+
+ def unregister_agent_service(self):
+ return shellutil.run("chkconfig --del waagent", chk_err=False)
+
+ def asn1_to_ssh_rsa(self, pubkey):
+ lines = pubkey.split("\n")
+ lines = [x for x in lines if not x.startswith("----")]
+ base64_encoded = "".join(lines)
+ try:
+ #TODO remove pyasn1 dependency
+ from pyasn1.codec.der import decoder as der_decoder
+ der_encoded = base64.b64decode(base64_encoded)
+ der_encoded = der_decoder.decode(der_encoded)[0][1]
+ key = der_decoder.decode(self.bits_to_bytes(der_encoded))[0]
+ n=key[0]
+ e=key[1]
+ keydata = bytearray()
+ keydata.extend(struct.pack('>I', len("ssh-rsa")))
+ keydata.extend(b"ssh-rsa")
+ keydata.extend(struct.pack('>I', len(self.num_to_bytes(e))))
+ keydata.extend(self.num_to_bytes(e))
+ keydata.extend(struct.pack('>I', len(self.num_to_bytes(n)) + 1))
+ keydata.extend(b"\0")
+ keydata.extend(self.num_to_bytes(n))
+ keydata_base64 = base64.b64encode(bytebuffer(keydata))
+ return text(b"ssh-rsa " + keydata_base64 + b"\n",
+ encoding='utf-8')
+ except ImportError as e:
+ raise OSUtilError("Failed to load pyasn1.codec.der")
+
+ def num_to_bytes(self, num):
+ """
+ Pack number into bytes. Retun as string.
+ """
+ result = bytearray()
+ while num:
+ result.append(num & 0xFF)
+ num >>= 8
+ result.reverse()
+ return result
+
+ def bits_to_bytes(self, bits):
+ """
+ Convert an array contains bits, [0,1] to a byte array
+ """
+ index = 7
+ byte_array = bytearray()
+ curr = 0
+ for bit in bits:
+ curr = curr | (bit << index)
+ index = index - 1
+ if index == -1:
+ byte_array.append(curr)
+ curr = 0
+ index = 7
+ return bytes(byte_array)
+
+ def openssl_to_openssh(self, input_file, output_file):
+ pubkey = fileutil.read_file(input_file)
+ ssh_rsa_pubkey = self.asn1_to_ssh_rsa(pubkey)
+ fileutil.write_file(output_file, ssh_rsa_pubkey)
+
+ #Override
+ def get_dhcp_pid(self):
+ ret= shellutil.run_get_output("pidof dhclient")
+ return ret[1] if ret[0] == 0 else None
+
+class RedhatOSUtil(Redhat6xOSUtil):
+ def __init__(self):
+ super(RedhatOSUtil, self).__init__()
+
+ def set_hostname(self, hostname):
+ super(RedhatOSUtil, self).set_hostname(hostname)
+ fileutil.update_conf_file('/etc/sysconfig/network',
+ 'HOSTNAME',
+ 'HOSTNAME={0}'.format(hostname))
+
+ def set_dhcp_hostname(self, hostname):
+ ifname = self.get_if_name()
+ filepath = "/etc/sysconfig/network-scripts/ifcfg-{0}".format(ifname)
+ fileutil.update_conf_file(filepath,
+ 'DHCP_HOSTNAME',
+ 'DHCP_HOSTNAME={0}'.format(hostname))
+
+ def register_agent_service(self):
+ return shellutil.run("systemctl enable waagent", chk_err=False)
+
+ def unregister_agent_service(self):
+ return shellutil.run("systemctl disable waagent", chk_err=False)
+
+
diff --git a/azurelinuxagent/distro/suse/__init__.py b/azurelinuxagent/distro/suse/__init__.py
new file mode 100644
index 0000000..4b2b9e1
--- /dev/null
+++ b/azurelinuxagent/distro/suse/__init__.py
@@ -0,0 +1,19 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
diff --git a/azurelinuxagent/distro/suse/loader.py b/azurelinuxagent/distro/suse/loader.py
new file mode 100644
index 0000000..e38aa17
--- /dev/null
+++ b/azurelinuxagent/distro/suse/loader.py
@@ -0,0 +1,29 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+from azurelinuxagent.metadata import DISTRO_NAME, DISTRO_VERSION, DISTRO_FULL_NAME
+
+def get_osutil():
+ from azurelinuxagent.distro.suse.osutil import SUSE11OSUtil, SUSEOSUtil
+ if DISTRO_FULL_NAME=='SUSE Linux Enterprise Server' and DISTRO_VERSION < '12' \
+ or DISTRO_FULL_NAME == 'openSUSE' and DISTRO_VERSION < '13.2':
+ return SUSE11OSUtil()
+ else:
+ return SUSEOSUtil()
+
diff --git a/azurelinuxagent/distro/suse/osutil.py b/azurelinuxagent/distro/suse/osutil.py
new file mode 100644
index 0000000..870e0b7
--- /dev/null
+++ b/azurelinuxagent/distro/suse/osutil.py
@@ -0,0 +1,88 @@
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import os
+import re
+import pwd
+import shutil
+import socket
+import array
+import struct
+import fcntl
+import time
+import azurelinuxagent.logger as logger
+import azurelinuxagent.utils.fileutil as fileutil
+import azurelinuxagent.utils.shellutil as shellutil
+import azurelinuxagent.utils.textutil as textutil
+from azurelinuxagent.metadata import DISTRO_NAME, DISTRO_VERSION, DISTRO_FULL_NAME
+from azurelinuxagent.distro.default.osutil import DefaultOSUtil
+
+class SUSE11OSUtil(DefaultOSUtil):
+ def __init__(self):
+ super(SUSE11OSUtil, self).__init__()
+ self.dhclient_name='dhcpcd'
+
+ def set_hostname(self, hostname):
+ fileutil.write_file('/etc/HOSTNAME', hostname)
+ shellutil.run("hostname {0}".format(hostname), chk_err=False)
+
+ def get_dhcp_pid(self):
+ ret= shellutil.run_get_output("pidof {0}".format(self.dhclient_name))
+ return ret[1] if ret[0] == 0 else None
+
+ def is_dhcp_enabled(self):
+ return True
+
+ def stop_dhcp_service(self):
+ cmd = "/sbin/service {0} stop".format(self.dhclient_name)
+ return shellutil.run(cmd, chk_err=False)
+
+ def start_dhcp_service(self):
+ cmd = "/sbin/service {0} start".format(self.dhclient_name)
+ return shellutil.run(cmd, chk_err=False)
+
+ def start_network(self) :
+ return shellutil.run("/sbin/service start network", chk_err=False)
+
+ def restart_ssh_service(self):
+ return shellutil.run("/sbin/service sshd restart", chk_err=False)
+
+ def stop_agent_service(self):
+ return shellutil.run("/sbin/service waagent stop", chk_err=False)
+
+ def start_agent_service(self):
+ return shellutil.run("/sbin/service waagent start", chk_err=False)
+
+ def register_agent_service(self):
+ return shellutil.run("/sbin/insserv waagent", chk_err=False)
+
+ def unregister_agent_service(self):
+ return shellutil.run("/sbin/insserv -r waagent", chk_err=False)
+
+class SUSEOSUtil(SUSE11OSUtil):
+ def __init__(self):
+ super(SUSEOSUtil, self).__init__()
+ self.dhclient_name = 'wickedd-dhcp4'
+
+ def register_agent_service(self):
+ return shellutil.run("systemctl enable waagent", chk_err=False)
+
+ def unregister_agent_service(self):
+ return shellutil.run("systemctl disable waagent", chk_err=False)
+
+
diff --git a/azurelinuxagent/distro/ubuntu/__init__.py b/azurelinuxagent/distro/ubuntu/__init__.py
new file mode 100644
index 0000000..4b2b9e1
--- /dev/null
+++ b/azurelinuxagent/distro/ubuntu/__init__.py
@@ -0,0 +1,19 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
diff --git a/azurelinuxagent/distro/ubuntu/deprovision.py b/azurelinuxagent/distro/ubuntu/deprovision.py
new file mode 100644
index 0000000..10fa123
--- /dev/null
+++ b/azurelinuxagent/distro/ubuntu/deprovision.py
@@ -0,0 +1,43 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import os
+import azurelinuxagent.logger as logger
+import azurelinuxagent.utils.fileutil as fileutil
+from azurelinuxagent.distro.default.deprovision import DeprovisionHandler, DeprovisionAction
+
+def del_resolv():
+ if os.path.realpath('/etc/resolv.conf') != '/run/resolvconf/resolv.conf':
+ logger.info("resolvconf is not configured. Removing /etc/resolv.conf")
+ fileutil.rm_files('/etc/resolv.conf')
+ else:
+ logger.info("resolvconf is enabled; leaving /etc/resolv.conf intact")
+ fileutil.rm_files('/etc/resolvconf/resolv.conf.d/tail',
+ '/etc/resolvconf/resolv.conf.d/originial')
+
+
+class UbuntuDeprovisionHandler(DeprovisionHandler):
+ def setup(self, deluser):
+ warnings, actions = super(UbuntuDeprovisionHandler, self).setup(deluser)
+ warnings.append("WARNING! Nameserver configuration in "
+ "/etc/resolvconf/resolv.conf.d/{tail,originial} "
+ "will be deleted.")
+ actions.append(DeprovisionAction(del_resolv))
+ return warnings, actions
+
diff --git a/azurelinuxagent/distro/ubuntu/handlerFactory.py b/azurelinuxagent/distro/ubuntu/handlerFactory.py
new file mode 100644
index 0000000..c8d0906
--- /dev/null
+++ b/azurelinuxagent/distro/ubuntu/handlerFactory.py
@@ -0,0 +1,29 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+from azurelinuxagent.distro.ubuntu.provision import UbuntuProvisionHandler
+from azurelinuxagent.distro.ubuntu.deprovision import UbuntuDeprovisionHandler
+from azurelinuxagent.distro.default.handlerFactory import DefaultHandlerFactory
+
+class UbuntuHandlerFactory(DefaultHandlerFactory):
+ def __init__(self):
+ super(UbuntuHandlerFactory, self).__init__()
+ self.provision_handler = UbuntuProvisionHandler()
+ self.deprovision_handler = UbuntuDeprovisionHandler()
+
diff --git a/azurelinuxagent/distro/ubuntu/loader.py b/azurelinuxagent/distro/ubuntu/loader.py
new file mode 100644
index 0000000..26db4fa
--- /dev/null
+++ b/azurelinuxagent/distro/ubuntu/loader.py
@@ -0,0 +1,36 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+from azurelinuxagent.metadata import DISTRO_NAME, DISTRO_VERSION
+
+def get_osutil():
+ from azurelinuxagent.distro.ubuntu.osutil import Ubuntu1204OSUtil, \
+ UbuntuOSUtil, \
+ Ubuntu14xOSUtil
+ if DISTRO_VERSION == "12.04":
+ return Ubuntu1204OSUtil()
+ elif DISTRO_VERSION == "14.04" or DISTRO_VERSION == "14.10":
+ return Ubuntu14xOSUtil()
+ else:
+ return UbuntuOSUtil()
+
+def get_handlers():
+ from azurelinuxagent.distro.ubuntu.handlerFactory import UbuntuHandlerFactory
+ return UbuntuHandlerFactory()
+
diff --git a/azurelinuxagent/distro/ubuntu/osutil.py b/azurelinuxagent/distro/ubuntu/osutil.py
new file mode 100644
index 0000000..1e51c2a
--- /dev/null
+++ b/azurelinuxagent/distro/ubuntu/osutil.py
@@ -0,0 +1,65 @@
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import os
+import re
+import pwd
+import shutil
+import socket
+import array
+import struct
+import fcntl
+import time
+import azurelinuxagent.logger as logger
+import azurelinuxagent.utils.fileutil as fileutil
+import azurelinuxagent.utils.shellutil as shellutil
+import azurelinuxagent.utils.textutil as textutil
+from azurelinuxagent.distro.default.osutil import DefaultOSUtil
+
+class Ubuntu14xOSUtil(DefaultOSUtil):
+ def __init__(self):
+ super(Ubuntu14xOSUtil, self).__init__()
+
+ def start_network(self):
+ return shellutil.run("service networking start", chk_err=False)
+
+ def stop_agent_service(self):
+ return shellutil.run("service walinuxagent stop", chk_err=False)
+
+ def start_agent_service(self):
+ return shellutil.run("service walinuxagent start", chk_err=False)
+
+class Ubuntu1204OSUtil(Ubuntu14xOSUtil):
+ def __init__(self):
+ super(Ubuntu1204OSUtil, self).__init__()
+
+ #Override
+ def get_dhcp_pid(self):
+ ret= shellutil.run_get_output("pidof dhclient3")
+ return ret[1] if ret[0] == 0 else None
+
+class UbuntuOSUtil(Ubuntu14xOSUtil):
+ def __init__(self):
+ super(UbuntuOSUtil, self).__init__()
+
+ def register_agent_service(self):
+ return shellutil.run("systemctl unmask walinuxagent", chk_err=False)
+
+ def unregister_agent_service(self):
+ return shellutil.run("systemctl mask walinuxagent", chk_err=False)
+
diff --git a/azurelinuxagent/distro/ubuntu/provision.py b/azurelinuxagent/distro/ubuntu/provision.py
new file mode 100644
index 0000000..7551074
--- /dev/null
+++ b/azurelinuxagent/distro/ubuntu/provision.py
@@ -0,0 +1,72 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import os
+import time
+import azurelinuxagent.logger as logger
+from azurelinuxagent.future import text
+import azurelinuxagent.conf as conf
+import azurelinuxagent.protocol as prot
+from azurelinuxagent.exception import *
+from azurelinuxagent.utils.osutil import OSUTIL
+import azurelinuxagent.utils.shellutil as shellutil
+import azurelinuxagent.utils.fileutil as fileutil
+from azurelinuxagent.distro.default.provision import ProvisionHandler
+
+"""
+On ubuntu image, provision could be disabled.
+"""
+class UbuntuProvisionHandler(ProvisionHandler):
+ def process(self):
+ #If provision is enabled, run default provision handler
+ if conf.get_switch("Provisioning.Enabled", False):
+ super(UbuntuProvisionHandler, self).process()
+ return
+
+ logger.info("run Ubuntu provision handler")
+ provisioned = os.path.join(OSUTIL.get_lib_dir(), "provisioned")
+ if os.path.isfile(provisioned):
+ return
+
+ logger.info("Waiting cloud-init to finish provisioning.")
+ protocol = prot.FACTORY.get_default_protocol()
+ try:
+ logger.info("Wait for ssh host key to be generated.")
+ thumbprint = self.wait_for_ssh_host_key()
+ fileutil.write_file(provisioned, "")
+
+ logger.info("Finished provisioning")
+ status = prot.ProvisionStatus(status="Ready")
+ status.properties.certificateThumbprint = thumbprint
+ protocol.report_provision_status(status)
+
+ except ProvisionError as e:
+ logger.error("Provision failed: {0}", e)
+ protocol.report_provision_status(status="NotReady", subStatus=text(e))
+
+ def wait_for_ssh_host_key(self, max_retry=60):
+ kepair_type = conf.get("Provisioning.SshHostKeyPairType", "rsa")
+ path = '/etc/ssh/ssh_host_{0}_key'.format(kepair_type)
+ for retry in range(0, max_retry):
+ if os.path.isfile(path):
+ return self.get_ssh_host_key_thumbprint(kepair_type)
+ if retry < max_retry - 1:
+ logger.info("Wait for ssh host key be generated: {0}", path)
+ time.sleep(5)
+ raise ProvisionError("Ssh hsot key is not generated.")