Repository: ansible/ansible

Issue:
alternatives module: requires link on Debian-based if no alternative exists yet
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
alternatives.py

https://github.com/ansible/ansible-modules-extras/blob/devel/system/alternatives.py#L148

##### ANSIBLE VERSION
```
$ ansible --version
ansible 2.3.0.0
  config file = /home/cytopia/repo/flaconi/devops-provisioning/ansible.cfg
  configured module search path = Default w/o overrides
  python version = 2.7.12 (default, Nov 19 2016, 06:48:10) [GCC 5.4.0 20160609]
```

##### CONFIGURATION
Nothing special.

##### OS / ENVIRONMENT
Debian Jessie

##### SUMMARY
The following is supposed to work on Debian-like systems.
```yml
- name: correct java version selected
  alternatives:
    name: java
    path: /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java
```

However, if there is no java alternative yet and you roll the above out for the first time, you get an error that `link` is required. The following will work

```yml
- name: correct java version selected
  alternatives:
    link: /usr/bin/java
    name: java
    path: /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java
```

This should not be required on Debian-like systems, only on RedHat and alike.

The corresponding line where `link` is checked, is here:

https://github.com/ansible/ansible-modules-extras/blob/devel/system/alternatives.py#L148

And (a little bit above), `link` will be filled, if there is an alternative already (determined by return code 0):

https://github.com/ansible/ansible-modules-extras/blob/devel/system/alternatives.py#L116

##### STEPS TO REPRODUCE

```yml
- name: correct java version selected
  alternatives:
    name: java
    path: /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java
```

On a Debian-lile system with no java alternative yet.


##### EXPECTED RESULTS
Succeed

##### ACTUAL RESULTS

Needed to install the alternative, but unable to do so as we are missing the link




Discussion:
cc @DavidWittman @mulby
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
@cytopia Does _if there is no java alternative_  mean that `update-alternatives --remove-all java` has been called ?
If not could you add outputs of `update-alternatives --display java` and `update-alternatives --query java` ?

@pilou- 

I don't know if `update-alternatives --remove-all java` has been called. At least not by me.
It was a fresh clean Debian install.

```shell
$ update-alternatives --display java
update-alternatives: error: no alternatives for java
$ echo $?
2
```

```shell
$ update-alternatives --query java
update-alternatives: error: no alternatives for java
$ echo $?
2
```
As the exit code is `2`, it does NOT go into this if: https://github.com/ansible/ansible-modules-extras/blob/devel/system/alternatives.py#L116, but then it will jump into this if: https://github.com/ansible/ansible-modules-extras/blob/devel/system/alternatives.py#L142:

```python
if current_path != path:
        if module.check_mode:
            module.exit_json(changed=True, current_path=current_path)
        try:
            # <-- IT WILL GO HERE
            if path not in all_alternatives:
                # <-- IT WILL GO HERE
                if not link:
                    # <-- IT WILL GO HERE and link is not set yet (because it skipped the first if)
                    module.fail_json(msg="Needed to install the alternative, but unable to do so as we are missing the link")

```
As far as I have understood it (not 100% sure).

1. Add new alternative: name: `java`, path: `/usr/local/bin/my-custom/bin/java`
2. Look up current alternative target path: e.g. `/etc/alternative/<name>`
3. Add `/usr/local/bin/my-custom/bin/java` to `/etc/alternative/<name>`

However, if `<name>` (In this case `java`) does not yet exist as an alternative, it can only be added when `link:` is specified in the module, otherwise it will fail.

So I guess it should use `<name>` for the alternative path
The current code use `update-alternatives --display java && update-alternatives --query java`. In your setup, how the value of the link could be found ? The alternative doesn't exist: I fails to see how the link could be found by the module: this link could be anything (`/usr/sbin/java` or `/usr/lib/java` or `/usr/share/java`).

Note that could use `dpkg-reconfigure` in order to recreate an alternative:
```
$ update-alternatives --list java
/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java
$ sudo update-alternatives --remove-all java
$ update-alternatives --list java
update-alternatives: error: no alternatives for java
$ sudo dpkg-reconfigure openjdk-8-jre-headless
update-alternatives: using /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java to provide /usr/bin/java (java) in auto mode
$ sudo update-alternatives --list java
/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java
```
When looking at the module example:
```yml
- name: correct java version selected
  alternatives:
    name: java
    path: /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java
```
Couldn't you just set the initial alternative by the `path`, if there is none yet?

The link points to `/etc/alternatives/<name>`, which is itself a link to the `path` parameter:
```
$ ls -l /usr/bin/java
lrwxrwxrwx 1 root root 22 Jun 14 11:02 /usr/bin/java -> /etc/alternatives/java
$ ls -l /etc/alternatives/java
lrwxrwxrwx 1 root root 46 Jun 14 11:02 /etc/alternatives/java -> /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java
```

According to the commands below, I am not sure how the initial alternative can be set by the path ?
```
$ sudo update-alternatives --remove-all java
$ ls -l /usr/bin/java
ls: cannot access '/usr/bin/java': No such file or directory
$ ls -l /etc/alternatives/java
ls: cannot access '/etc/alternatives/java': No such file or directory
$ ls -l /var/lib/dpkg/alternatives/java
ls: cannot access '/var/lib/dpkg/alternatives/java': No such file or directory
```

I don't know how `/usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java` could be there without the `java` alternative.
One way could be to either use

1. path only
2. or path and name:

### 1. path only:

```yml
- name: correct java version selected
  alternatives:
    name: java
    path: /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java
```

altpath: ` /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java`
altname: `$ basename  /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java` (evaluates to `java`)

Setting:

```shell
/usr/bin/<altname> -> /etc/alternatives/<altname>
/etc/alternatives/<altname> -> <altpath>
```

### 2. or path only:

```yml
- name: correct java version selected
  alternatives:
    name: java
    path: /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java
```

altpath: `$ dirname /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java` (remove the `java` bit)
altname: `java` (set in `name: java`)

Setting:

```shell
/usr/bin/<altname> -> /etc/alternatives/<altname>
/etc/alternatives/<altname> -> <altpath>/<altnane>
```



It seems to me that the problem is related to the `/usr/bin/` part of `/usr/bin/<altname>`: it could be `/usr/sbin/<altname>` instead.
Yes you're right.
But then it also means, I always have to specify `link:` when using this module, just to be on the safe side.
On Debian Jessie, `openjdk-7-jre-headless` package contains `/usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java` and configure the `java` alternative.

You might check in `/var/log/alternatives.log*` files why `java` alternative has been removed.
On my system, I am downloading Oracle Java directly (No other Java installed). Once extracted and installed I was just trying to set the java alternative.

And that unfortunately only works when using `link:`.
@pilou- I would suggest making `link:` a required option or at least adding a note for custom installed packages
@cytopia: what do you think of:
> This option is always required on RHEL-based distributions. On Debian-based
distributions this option is required when the alternative is unknown to the system.

@pilou- I think this is a good solution. I would also check, if an alternative is available prior execution (if `link` is not set).

Resolving patch:
diff --git a/lib/ansible/modules/system/alternatives.py b/lib/ansible/modules/system/alternatives.py
--- a/lib/ansible/modules/system/alternatives.py
+++ b/lib/ansible/modules/system/alternatives.py
@@ -37,7 +37,8 @@
   link:
     description:
       - The path to the symbolic link that should point to the real executable.
-      - This option is required on RHEL-based distributions
+      - This option is always required on RHEL-based distributions. On Debian-based distributions this option is
+        required when the alternative I(name) is unknown to the system.
     required: false
   priority:
     description:


Test patch: