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
resolved_by_pr 23037
#23037 merged. Closing.
This does not seem fixed. Specifically, if the `label` is specified, the partition table is always recreated, even if it already exists.

Running the following task:

```yaml
- name: Create partition
  become: yes
  parted:
    label: gpt
    state: present
    device: /dev/vda
    number: 1
    name: foo
```

Causes the results below, alternating on each run. Note the second destroys the partition:

```
TASK [Create partition] ********************************************************************************
changed: [debianvm] => {"changed": true, "disk": {"dev": "/dev/vda", "logical_block": 512, "model": "Virtio Block Device", "physical_block": 512, "size": 10485760.0, "table": "gpt", "unit": "kib"}, "partitions": [{"begin": 1024.0, "end": 10484736.0, "flags": [], "fstype": "", "num": 1, "size": 10483712.0, "unit": "kib"}], "script": "unit KiB mklabel gpt mkpart primary 0% 100% unit KiB name 1 foo"}
```

```
TASK [Create partition] ********************************************************************************
changed: [debianvm] => {"changed": true, "disk": {"dev": "/dev/vda", "logical_block": 512, "model": "Virtio Block Device", "physical_block": 512, "size": 10485760.0, "table": "gpt", "unit": "kib"}, "partitions": [], "script": "unit KiB mklabel gpt"}
```
Please have a look at #22996, it fixes the issue reported by @kwohlfahrt 
The issue I reported appears fixed. There is a different issue now, in that the sizes are not correctly checked:

```
    - name: Create EFI partitions
      become: yes
      parted:
        label: gpt
        state: present
        device: /dev/vda
        part_start: 0%
        part_end: 512MiB
        number: 1
```

Results in:
```
changed: [vm] => {"changed": true, "disk": {"dev": "/dev/vda", "logical_block": 512, "model": "Virtio Block Device", "physical_block": 512, "size": 10485760.0, "table": "gpt", "unit": "kib"}, "partitions": [{"begin": 1024.0, "end": 524288.0, "flags": [], "fstype": "", "name": "primary", "num": 1, "size": 523264.0, "unit": "kib"}], "script": "unit KiB mkpart primary 0% 512MiB"}
```

And then:
```
ok: [vm] => {"changed": false, "disk": {"dev": "/dev/vda", "logical_block": 512, "model": "Virtio Block Device", "physical_block": 512, "size": 10485760.0, "table": "gpt", "unit": "kib"}, "partitions": [{"begin": 1024.0, "end": 524288.0, "flags": [], "fstype": "", "name": "primary", "num": 1, "size": 523264.0, "unit": "kib"}], "script": ""}
```

So far, this is expected. If I now edit the play to a different size (by changing `part_end`), it still reports OK and no change is made:

```
    - name: Create EFI partitions
      become: yes
      parted:
        label: gpt
        state: present
        device: /dev/vda
        part_start: 0%
        part_end: 1024MiB
        number: 1
```
```
ok: [vm] => {"changed": false, "disk": {"dev": "/dev/vda", "logical_block": 512, "model": "Virtio Block Device", "physical_block": 512, "size": 10485760.0, "table": "gpt", "unit": "kib"}, "partitions": [{"begin": 1024.0, "end": 524288.0, "flags": [], "fstype": "", "name": "primary", "num": 1, "size": 523264.0, "unit": "kib"}], "script": ""}
```

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
@@ -71,7 +71,7 @@
     description: Creates a new disk label.
     choices: [
        'aix', 'amiga', 'bsd', 'dvh', 'gpt', 'loop', 'mac', 'msdos', 'pc98',
-       'sun', ''
+       'sun'
     ]
     default: msdos
   part_type:
@@ -81,6 +81,7 @@
        'gpt' partition table. Neither part-type nor name may be used with a
        'sun' partition table.
     choices: ['primary', 'extended', 'logical']
+    default: primary
   part_start:
     description:
      - Where the partition will start as offset from the beginning of the disk,
@@ -135,14 +136,16 @@
           "begin": 0.0,
           "end": 1.0,
           "flags": ["boot", "lvm"],
-          "fstype": null,
+          "fstype": "",
+          "name": "",
           "num": 1,
           "size": 1.0
         }, {
           "begin": 1.0,
           "end": 5.0,
           "flags": [],
-          "fstype": null,
+          "fstype": "",
+          "name": "",
           "num": 2,
           "size": 4.0
         }]
@@ -298,11 +301,13 @@ def parse_partition_info(parted_output, unit):
         if unit != 'chs':
             size = parse_unit(part_params[3])[0]
             fstype = part_params[4]
+            name = part_params[5]
             flags = part_params[6]
 
         else:
             size = ""
             fstype = part_params[3]
+            name = part_params[4]
             flags = part_params[5]
 
         parts.append({
@@ -311,6 +316,7 @@ def parse_partition_info(parted_output, unit):
             'end': parse_unit(part_params[2])[0],
             'size': size,
             'fstype': fstype,
+            'name': name,
             'flags': [f.strip() for f in flags.split(', ') if f != ''],
             'unit': unit.lower(),
         })
@@ -555,7 +561,7 @@ def main():
             'label': {
                 'choices': [
                     'aix', 'amiga', 'bsd', 'dvh', 'gpt', 'loop', 'mac', 'msdos',
-                    'pc98', 'sun', ''
+                    'pc98', 'sun'
                 ],
                 'type': 'str'
             },
@@ -623,13 +629,11 @@ def main():
 
     if state == 'present':
         # Default value for the label
-        if not current_device['generic']['table'] or \
-           current_device['generic']['table'] == 'unknown' and \
-           not label:
+        if not label:
             label = 'msdos'
 
         # Assign label if required
-        if label:
+        if current_device['generic'].get('table', None) != label:
             script += "mklabel %s " % label
 
         # Create partition if required
@@ -660,7 +664,7 @@ def main():
                 partition = [p for p in current_parts if p['num'] == number][0]
 
             # Assign name to the the partition
-            if name:
+            if name is not None and partition.get('name', None) != name:
                 script += "name %s %s " % (number, name)
 
             # Manage flags


Test patch: