Repository: ansible/ansible-modules-core

Issue:
hostname: support alpine(3.4)
##### ISSUE TYPE
- Feature Idea
##### COMPONENT NAME

hostname
##### ANSIBLE VERSION

<!--- Paste verbatim output from “ansible --version” between quotes below -->

```
ansible 2.2.0 (devel e326da28ff) last updated 2016/09/13 13:46:08 (GMT -300)
  lib/ansible/modules/core: (detached HEAD ae6992bf8c) last updated 2016/09/13 13:47:28 (GMT -300)
  lib/ansible/modules/extras: (detached HEAD afd0b23836) last updated 2016/09/13 13:47:28 (GMT -300)
  config file = /Users/jbergstroem/Work/node/build/ansible/ansible.cfg
  configured module search path = ['plugins/library']
```
##### CONFIGURATION
##### OS / ENVIRONMENT

N/A
##### SUMMARY

Ansible currently returns this if you try to use the hostname module on alpine:
`fatal: [test-joyent-alpine34-x64-2]: FAILED! => {"changed": false, "failed": true, "msg": "hostname module cannot be used on platform Linux (Alpine)"}`

Supporting it should be relatively straightforward seeing how it utilizes both `/etc/hostname` and `hostname` if busybox is installed. I can see the rationale for not supporting a lot of stuff on alpine but I think people that use it to a point where even busybox isn't installed wouldn't try and call hostname either.
##### STEPS TO REPRODUCE

Just call hostname
##### EXPECTED RESULTS

Setting the hostname
##### ACTUAL RESULTS

```
fatal: [test-joyent-alpine34-x64-2]: FAILED! => {"changed": false, "failed": true, "msg": "hostname module cannot be used on platform Linux (Alpine)"}
```



Discussion:
<!-- @jbergstroem: Thanks for filing this feature idea! -->

@hnakamur, @saito-hideki, ping. This issue is waiting on your response.
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)

<!--- boilerplate: issue_bug_new --->


Resolving patch:
diff --git a/system/hostname.py b/system/hostname.py
--- a/system/hostname.py
+++ b/system/hostname.py
@@ -29,7 +29,7 @@
 requirements: [ hostname ]
 description:
     - Set system's hostname.
-    - Currently implemented on Debian, Ubuntu, Fedora, RedHat, openSUSE, Linaro, ScientificLinux, Arch, CentOS, AMI.
+    - Currently implemented on Debian, Ubuntu, Fedora, RedHat, openSUSE, Linaro, ScientificLinux, Arch, CentOS, AMI, Alpine Linux.
     - Any distribution that uses systemd as their init system.
     - Note, this module does *NOT* modify /etc/hosts. You need to modify it yourself using other modules like template or replace.
 options:
@@ -56,6 +56,15 @@ class UnimplementedStrategy(object):
     def __init__(self, module):
         self.module = module
 
+    def update_current_and_permanent_hostname(self):
+        self.unimplemented_error()
+
+    def update_current_hostname(self):
+        self.unimplemented_error()
+
+    def update_permanent_hostname(self):
+        self.unimplemented_error()
+
     def get_current_hostname(self):
         self.unimplemented_error()
 
@@ -103,6 +112,9 @@ def __init__(self, module):
         else:
             self.strategy = self.strategy_class(module)
 
+    def update_current_and_permanent_hostname(self):
+        return self.strategy.update_current_and_permanent_hostname()
+
     def get_current_hostname(self):
         return self.strategy.get_current_hostname()
 
@@ -129,6 +141,26 @@ class GenericStrategy(object):
     def __init__(self, module):
         self.module = module
         self.hostname_cmd = self.module.get_bin_path('hostname', True)
+        self.changed = False
+
+    def update_current_and_permanent_hostname(self):
+        self.update_current_hostname()
+        self.update_permanent_hostname()
+        return self.changed
+
+    def update_current_hostname(self):
+        name = self.module.params['name']
+        current_name = self.get_current_hostname()
+        if current_name != name:
+            self.set_current_hostname(name)
+            self.changed = True
+
+    def update_permanent_hostname(self):
+        name = self.module.params['name']
+        permanent_name = self.get_permanent_hostname()
+        if permanent_name != name:
+            self.set_permanent_hostname(name)
+            self.changed = True
 
     def get_current_hostname(self):
         cmd = [self.hostname_cmd]
@@ -283,6 +315,59 @@ def set_permanent_hostname(self, name):
             self.module.fail_json(msg="failed to update hostname: %s" %
                 str(err))
 
+# ===========================================
+
+class AlpineStrategy(GenericStrategy):
+    """
+    This is a Alpine Linux Hostname manipulation strategy class - it edits
+    the /etc/hostname file then run hostname -F /etc/hostname.
+    """
+
+    HOSTNAME_FILE = '/etc/hostname'
+
+    def update_current_and_permanent_hostname(self):
+        self.update_permanent_hostname()
+        self.update_current_hostname()
+        return self.changed
+
+    def get_permanent_hostname(self):
+        if not os.path.isfile(self.HOSTNAME_FILE):
+            try:
+                open(self.HOSTNAME_FILE, "a").write("")
+            except IOError:
+                err = get_exception()
+                self.module.fail_json(msg="failed to write file: %s" %
+                    str(err))
+        try:
+            f = open(self.HOSTNAME_FILE)
+            try:
+                return f.read().strip()
+            finally:
+                f.close()
+        except Exception:
+            err = get_exception()
+            self.module.fail_json(msg="failed to read hostname: %s" %
+                str(err))
+
+    def set_permanent_hostname(self, name):
+        try:
+            f = open(self.HOSTNAME_FILE, 'w+')
+            try:
+                f.write("%s\n" % name)
+            finally:
+                f.close()
+        except Exception:
+            err = get_exception()
+            self.module.fail_json(msg="failed to update hostname: %s" %
+                str(err))
+
+    def set_current_hostname(self, name):
+        cmd = [self.hostname_cmd, '-F', self.HOSTNAME_FILE]
+        rc, out, err = self.module.run_command(cmd)
+        if rc != 0:
+            self.module.fail_json(msg="Command failed rc=%d, out=%s, err=%s" %
+                (rc, out, err))
+
 
 # ===========================================
 
@@ -612,6 +697,11 @@ class ALTLinuxHostname(Hostname):
     distribution = 'Altlinux'
     strategy_class = RedHatStrategy
 
+class AlpineLinuxHostname(Hostname):
+    platform = 'Linux'
+    distribution = 'Alpine'
+    strategy_class = AlpineStrategy
+
 class OpenBSDHostname(Hostname):
     platform = 'OpenBSD'
     distribution = None
@@ -638,18 +728,8 @@ def main():
     )
 
     hostname = Hostname(module)
-
-    changed = False
     name = module.params['name']
-    current_name = hostname.get_current_hostname()
-    if current_name != name:
-        hostname.set_current_hostname(name)
-        changed = True
-
-    permanent_name = hostname.get_permanent_hostname()
-    if permanent_name != name:
-        hostname.set_permanent_hostname(name)
-        changed = True
+    changed = hostname.update_current_and_permanent_hostname()
 
     module.exit_json(changed=changed, name=name,
                      ansible_facts=dict(ansible_hostname=name.split('.')[0],


Test patch: