Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

create content_view_rollback role #1217

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions roles/content_view_rollback/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
theforeman.foreman.content_view_rollback
=========

A role for automating the rollback of Content-Views.

Requirements
------------

This role requires the theforeman.foreman module collection.

Role Variables
--------------

The primary dictionary is the organizations dictionary, which is formatted as such:
```
organizations:
org1:
lifecycle_environments:
- "Dev"
- "QA"
- "Prod"
content_views:
- "content-view1"
- "content-view2"
org2:
lifecycle_environments:
- "Dev2"
- "QA2"
- "Prod2"
content_views:
- "content-view3"
- "content-view4"
```

This can run against multiple organizations/lifecycle_environments/content-views or selected subsets.

For example, if the previously mentioned dictionary describes *ALL* of my Foreman environment, but I only want to rollback the 'Prod' lifecycle_environment in the content-view 'content-view1' in organization 'org', my dictionary would look like this:
```
organizations:
org1:
lifecycle_environments:
- "Prod"
content_views:
- "content-view1"
```
Items not described in the inventory will not be affected.

Dependencies
------------

You need a Foreman user with admin access to the Organizations, Lifecycle_Environments, and Content_Views you wish to interact with.

By default, the role will require a valid SSL certificate installed on your Foreman server that the ansible client can trace trust to. To disable that update the 'FOREMAN_VALIDATE_CERTS' variable in defaults/main.yml.

For example, to disable certificate checking you would update the variable as such:
```
FOREMAN_VALIDATE_CERTS: false
```

Example Playbook
----------------

The role can be instantiated quite simply, all of the decision making is handled by the variables previously set:

```
---
- name: "Run the content_view_rollback Role"
hosts: all
tasks:
- name: "Run the content_view_rollback Role"
include_role:
name: theforeman.foreman.content_view_rollback
```
For example:

Rolling back Lifecycle Environments inside their respective Content-Views to the previous version:
```
organizations:
org1:
lifecycle_environments:
- "Dev"
- "QA"
- "Prod"
content_views:
- "content-view1"
- "content-view2"
```

The role would take the Dev, QA and Prod Lifecycle Environments to Content-View version N-1. If, prior to role runtime, the versions were: Prod=10, QA=11, and Dev=12, the result at the end of the run would be: Prod=9, QA=10, and Dev=11. If that Content-View version does not exist it will select the next lowest Content-View version. If there are none lower, it will exit with a message saying such.

To perform actions across multiple Organizations:
```
organizations:
org1:
lifecycle_environments:
- "Dev"
- "QA"
- "Prod"
content_views:
- "content-view1"
- "content-view2"
org2:
lifecycle_environments:
- "LCE1"
- "LCE2"
content_views:
- "org2_content-view"
```
3 changes: 3 additions & 0 deletions roles/content_view_rollback/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
# defaults file for content_view_promotion_rollback_publish
FOREMAN_VALIDATE_CERTS: true
6 changes: 6 additions & 0 deletions roles/content_view_rollback/tasks/content-view.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
- name: "Rollback Content-Views"
include_tasks: rollback.yml
loop: "{{ organization.value.content_views }}"
loop_control:
loop_var: content_view
6 changes: 6 additions & 0 deletions roles/content_view_rollback/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
- name: "Run Content-View Role"
include_tasks: content-view.yml
with_dict: "{{ organizations }}"
loop_control:
loop_var: organization
90 changes: 90 additions & 0 deletions roles/content_view_rollback/tasks/rollback.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
---
# get data on the current content-view
- name: "Gather Data For Current Content-View From Foreman"
theforeman.foreman.resource_info:
username: "{{ foreman_user }}"
password: "{{ foreman_password }}"
server_url: "{{ foreman_server_url }}"
organization: "{{ organization.key }}"
resource: content_views
search: name = "{{ content_view }}"
validate_certs: false
register: content_view_data

# get data on the current content-view version
- name: "Gather Data For Current Content-View Versions From Foreman"
theforeman.foreman.resource_info:
username: "{{ foreman_user }}"
password: "{{ foreman_password }}"
server_url: "{{ foreman_server_url }}"
organization: "{{ organization.key }}"
resource: content_view_versions
params:
content_view_id: "{{ content_view_data.resources[0].id }}"
register: version_information

# creates a dictionary with data formatted as such {'Prod':'11.0'}
- name: "Build Dictionary With Lifecycle Envrionment And Version Number"
set_fact:
environments: "{{ environments | default({}) | combine ({item[1].name : item[0].name.split()[-1]}) }}"
with_subelements:
- "{{ version_information.resources }}"
- environments

# create list of content-view versions
- name: "Build List of All Versions of Content-View"
set_fact:
cv_versions: "{{ cv_versions | default([]) + [item.major] }}"
with_items: "{{ version_information.resources }}"

# set the highest version to zero so that we don't use previous Content-View settings
- name: "Set Lowest Version to 0"
set_fact:
lowest_version: 0

# set highest number
- name: "Set the Lowest Version of the Content-View Currently Available"
set_fact:
lowest_version: "{{ cv_versions | min }}"

# add one to each of the version numbers
- name: "Update Facts With Incremented Content-View Version Numbers"
set_fact:
new_environments: "{{ new_environments | default({}) | combine({item.key: item.value|int - 1.0 }) }}"
with_dict: "{{ environments }}"

# check if each of the N-1 versions exist
- name: "Check to make sure previous version exists"
set_fact:
non_environments: "{{ non_environments | default({}) | combine({item.key: item.value }) }}"
with_dict: "{{ new_environments }}"
when: item.value not in cv_versions

- name: "Check for next closest, lower version"
include_tasks: version.yml
with_dict: "{{ non_environments }}"
loop_control:
loop_var: version

# check if each of the N-1 versions exist
- name: "Check to make sure previous version exists"
fail:
msg: "There is no version lower than {{ item.value }} to roll back to for Content-View {{ item.key }}. Stopping execution."
with_dict: "{{ new_environments }}"
when: item.value not in cv_versions

# only promote environments defined in the vars
- name: "Rollback Environments to Version N-1"
theforeman.foreman.content_view_version:
username: "{{ foreman_user }}"
password: "{{ foreman_password }}"
server_url: "{{ foreman_server_url }}"
organization: "{{ organization.key }}"
content_view: "{{ content_view }}"
# dictionaries aren't ordered and Foreman doesn't want you promoting things out of order
# but we're promoting them all so we just override that behavior
force_promote: true
lifecycle_environments: "{{ item.key }}"
version: "{{ item.value }}"
with_dict: "{{ new_environments }}"
when: item.key in organization.value.lifecycle_environments
8 changes: 8 additions & 0 deletions roles/content_view_rollback/tasks/version.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
# reset the var value to the next lowest value
# reversing the list and iterating downwards until first match would be more efficient
- name: "Get closest lower version"
set_fact:
new_environments: "{{ new_environments | combine({version.key: item}) }}"
with_items: "{{ cv_versions | sort | list }}"
when: item | int < version.value | int
Comment on lines +4 to +8
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Maybe this can be turned into a filter plugin.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which part do you think would lend itself best to being a filter plugin?

My main concern here, is that we are pretty much handling this with 3 lines right now, and (at least after my cursory glance) adding a filter plugin would also require more code and perhaps make this a bit more confusing.

However, filters for something like 'get_next_lowest' integer in a list and 'get_next_highest' integer in a list might be good ideas for core contributions.

If that's of interest I can always make a PR over there.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking exactly about that kind of arithmetic. It was my first thought when reading the task name, and was reinforced by seeing set_fact. I'm fine if we say, that's a task for another PR on a another day.