Repository: ansible/ansible

Issue:
[parted] module 'parted' is not idempotent
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and master branch are affected too.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Bug Report

##### COMPONENT NAME
<!--- Name of the module/plugin/task/feature -->
parted

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
ansible 2.3.0.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = Default w/o overrides
  python version = 2.7.5 (default, Nov  6 2016, 00:28:07) [GCC 4.8.5 20150623 (Red Hat 4.8.5-11)]
```
git pull from stable-2.3 branch and 'make rpm'
$ rpm -qa | grep ^ansible
ansible-2.3.0.0-100.git201703241924.58f002d.stable23.el7.centos.noarch


##### CONFIGURATION
<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->

##### OS / ENVIRONMENT
<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say “N/A” for anything that is not platform-specific.
-->
CentOS 7.3 x86_64

##### SUMMARY
<!--- Explain the problem briefly -->
First run of parted module is successful.
Second run of parted module is not idempotent, returns "parted: invalid token: primary"
##### STEPS TO REPRODUCE
<!---
For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used.
-->

<!--- Paste example playbooks or commands between quotes below -->
```
ansible-playbook config-partition.yml -s -K --extra-vars="target=somemachine.domain.tld"
```
Inside a role is the following:
```
# Install parted
- name: Install parted
  yum:
    name: parted
    state: present

# Use entire storage for /dev/sdb1 (default)
- name: NFS parted /dev/sdb
  parted:
    label: gpt
    device: /dev/sdb
    number: 1
    flags: [ lvm ]
    state: present
  environment:
    PATH: "/usr/bin:/usr/sbin"
  become: yes
```
Returns with a "rc": 1 and "err": "parted: invalid token: primary\n"

}

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
I expected to be able to use the 'parted' module in an idempotent way without trapping "rc: 1" errors.
##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->

<!--- Paste verbatim command output between quotes below -->
```
Verbose output:
fatal: [somemachine.domain.tld]: FAILED! => {
    "changed": false,
    "err": "parted: invalid token: primary\n",
    "failed": true,
    "invocation": {
        "module_args": {
            "align": "optimal",
            "device": "/dev/sdb",
            "flags": [
                "lvm"
            ],
            "label": "gpt",
            "name": null,
            "number": 1,
            "part_end": "100%",
            "part_start": "0%",
            "part_type": "primary",
            "state": "present",
            "unit": "KiB"
        }
    },
    "msg": "Error while running parted script: parted -s -m -a optimal /dev/sdb -- unit KiB set 1 lvm on set 1 primary off",
    "out": "",
    "rc": 1
}
```



Discussion:
cc @ColOfAbRiX
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
It seems the code that manages flags is receiving wrong information. Let me dig more

Resolving patch:
diff --git a/lib/ansible/modules/system/parted.py b/lib/ansible/modules/system/parted.py
--- a/lib/ansible/modules/system/parted.py
+++ b/lib/ansible/modules/system/parted.py
@@ -167,7 +167,7 @@
     device: /dev/sdb
     number: 1
     state: present
-    part_end: 1gib
+    part_end: 1GiB
 
 # Create a new primary partition for LVM
 - parted:
@@ -175,7 +175,7 @@
     number: 2
     flags: [ lvm ]
     state: present
-    part_start: 1gib
+    part_start: 1GiB
 
 # Read device information (always use unit when probing)
 - parted: device=/dev/sdb unit=MiB
@@ -299,11 +299,12 @@ def parse_partition_info(parted_output, unit):
         if unit != 'chs':
             size   = parse_unit(part_params[3])[0]
             fstype = part_params[4]
-            flags  = part_params[5]
+            flags  = part_params[6]
+
         else:
             size   = ""
             fstype = part_params[3]
-            flags  = part_params[4]
+            flags  = part_params[5]
 
         parts.append({
             'num':    int(part_params[0]),
@@ -406,7 +407,7 @@ def get_device_info(device, unit):
     Fetches information about a disk and its partitions and it returns a
     dictionary.
     """
-    global module
+    global module, parted_exec
 
     # If parted complains about missing labels, it means there are no partitions.
     # In this case only, use a custom function to fetch information and emulate
@@ -415,7 +416,7 @@ def get_device_info(device, unit):
     if label_needed:
         return get_unlabeled_device_info(device, unit)
 
-    command = "parted -s -m %s -- unit '%s' print" % (device, unit)
+    command = "%s -s -m %s -- unit '%s' print" % (parted_exec, device, unit)
     rc, out, err = module.run_command(command)
     if rc != 0 and 'unrecognised disk label' not in err:
         module.fail_json(msg=(
@@ -433,13 +434,15 @@ def check_parted_label(device):
     to 3.1 don't return data when there is no label. For more information see:
     http://upstream.rosalinux.ru/changelogs/libparted/3.1/changelog.html
     """
+    global parted_exec
+
     # Check the version
     parted_major, parted_minor, _ = parted_version()
     if (parted_major == 3 and parted_minor >= 1) or parted_major > 3:
         return False
 
     # Older parted versions return a message in the stdout and RC > 0.
-    rc, out, err = module.run_command("parted -s -m %s print" % device)
+    rc, out, err = module.run_command("%s -s -m %s print" % (parted_exec, device))
     if rc != 0 and 'unrecognised disk label' in out.lower():
         return True
 
@@ -450,9 +453,9 @@ def parted_version():
     """
     Returns the major and minor version of parted installed on the system.
     """
-    global module
+    global module, parted_exec
 
-    rc, out, err = module.run_command("parted --version")
+    rc, out, err = module.run_command("%s --version" % parted_exec)
     if rc != 0:
         module.fail_json(
             msg="Failed to get parted version.", rc=rc, out=out, err=err
@@ -480,10 +483,10 @@ def parted(script, device, align):
     """
     Runs a parted script.
     """
-    global module
+    global module, parted_exec
 
     if script and not module.check_mode:
-        command = "parted -s -m -a %s %s -- %s" % (align, device, script)
+        command = "%s -s -m -a %s %s -- %s" % (parted_exec, align, device, script)
         rc, out, err = module.run_command(command)
 
         if rc != 0:
@@ -527,7 +530,7 @@ def check_size_format(size_str):
 
 
 def main():
-    global module, units_si, units_iec
+    global module, units_si, units_iec, parted_exec
 
     changed = False
     output_script = ""
@@ -596,6 +599,9 @@ def main():
     state       = module.params['state']
     flags       = module.params['flags']
 
+    # Parted executable
+    parted_exec = module.get_bin_path('parted', True)
+
     # Conditioning
     if number and number < 0:
         module.fail_json(msg="The partition number must be non negative.")


Test patch: