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

add general content-view management role #1177

Closed
wants to merge 11 commits into from
86 changes: 86 additions & 0 deletions roles/content_view_promotion_rollback_publish/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
theforeman.foreman.content_view_promotion_rollback_publish
=========

A role for automating the staging/promotion of Foreman Content-Views through various Organizations and LifeCycle Environemnts. Can also be used for publishing new versions as well as rolling back.

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

This role requires the theforeman.foreman module collection.

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

The main amount of work is vars/main.yml.
bmarlow marked this conversation as resolved.
Show resolved Hide resolved

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 promote 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 (the exception being Library when a new content_view_version needs to be published.
bmarlow marked this conversation as resolved.
Show resolved Hide resolved

If you only want to use this role for staging new content_view_versions, simply set the variable **publish_only** to **True** (this can be done in vars/main.yml or elsewhere). This action will only publish a new content_view_version to the content_views described in the dictionary.

If you want to rollback (reverse promote) lifecycle environments simply set the variable **rollback** to **True** (this can be done in vars/main.yml or elsewhere). This action will only roll back lifecycle_environments in the content views described in main dictionary.
bmarlow marked this conversation as resolved.
Show resolved Hide resolved


Foreman information is accessed by way of the dictionary 'foreman', formatted as such:
bmarlow marked this conversation as resolved.
Show resolved Hide resolved
```
foreman:
user: admin
password: changeme
url: "https://myforemanserver.myorg.com"
```
This is in the vars/main.yml for illistrative purposes only! Please use a vault (or custom credential-type if using Tower). STORING PASSWORDS IN PLAINTEXT IS BAD, MMM-KAY?


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.'
bmarlow marked this conversation as resolved.
Show resolved Hide resolved


Example Playbook
bmarlow marked this conversation as resolved.
Show resolved Hide resolved
----------------

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

```
---
- name: "Run the content_view_promotion_rollback_publish Role"
hosts: all
tasks:
- name: "Run the content_view_promotion_rollback_publish Role"
include_role:
name: theforeman.foreman.content_view_promotion_rollback_publish
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
# defaults file for content_view_promotion_rollback_publish
publish_only: false
rollback: false
FOREMAN_VALIDATE_CERTS: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
- name: "Promote Content-Views"
include_tasks: promote.yml
loop: "{{ organization.value.content_views }}"
loop_control:
loop_var: content_view
when: rollback == False

- name: "Rollback Content-Views"
include_tasks: rollback.yml
loop: "{{ organization.value.content_views }}"
loop_control:
loop_var: content_view
when: rollback == True
6 changes: 6 additions & 0 deletions roles/content_view_promotion_rollback_publish/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
82 changes: 82 additions & 0 deletions roles/content_view_promotion_rollback_publish/tasks/promote.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
# 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.url }}"
organization: "{{ organization.key }}"
resource: content_views
search: name = "{{ content_view }}"
validate_certs: false
register: content_view_data
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Author

Choose a reason for hiding this comment

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

Does it make sense to have roles that utilize modules that aren't even in a main version yet? By using the existing format, this role will be backwards compatible with older versions of the collection.

Copy link
Contributor

Choose a reason for hiding this comment

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

Chiming in here, I think it does make sense. The primary way we expect users would consume this role would be to install the collection.

If they install the development branch from git they would have access to all of the latest modules. Of course it is more likely that they install one of the released versions from ansible galaxy, or a downstream RPM packaged version.... in each case still by the time this role is included in any of those releases, so too would be all modules currently present at development time.

Is this contrary to your expectation, @bmarlow ?

Copy link
Author

Choose a reason for hiding this comment

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

@wbclark I think its fair to assume people are going to get this by way of the collection so no need to worry there.

I guess my statement is more that I don't think it makes sense to build a role against a module that is in development as changes that happen to that module could potentially break the role.

As it sits (and based on the way that roles are consumed/delivered), I would think we wouldn't care which module got used as long as it worked (unless there was a significant performance benefit or depreciation issue) due to the fact that the user shouldn't really be modifying the role, but rather changing the variables that the role digests.

I haven't had a chance to use the new module yet, however my biggest concern would be that the data returned isn't structured the same way (maybe it is?) and it would require more significant refactoring (and lets face it, the data returned isn't the most easily parsable.



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

Choose a reason for hiding this comment

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

There should be a dedicated module for this as well in the next few days: #1169

Could be good feedback before merging it whether it handles your use case.

Copy link
Author

Choose a reason for hiding this comment

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

Please see comment above.


# 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


# add 1 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 }}"


# set the highest version to zero so that we don't use previous loop settings
- name: "Set Highest Version to 0"
set_fact:
highest_version: 0


# get the highest version number for the content-view
- name: "Get Highest Content-View Version That Isn't Library"
set_fact:
highest_version: "{{ item.value|int }}"
when: highest_version | default(0) | int < item.value|int and item.key != 'Library'
with_dict: "{{ environments }}"


# only publish new view if necessary (when the current view is at or above library)
Copy link
Member

Choose a reason for hiding this comment

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

I do not fully understand this logic. Or the circumstances under which it may occur.

Copy link
Author

@bmarlow bmarlow Mar 16, 2021

Choose a reason for hiding this comment

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

If your content-view's highest version number is 22, and that is also where your life-cycle environment is then we need to publish a new version prior to promoting- since you cannot promote to version 22+1 if version 23 doesn't exist.

- name: "Publish new version of Content-View"
theforman.foreman.content_view_version:
username: "{{ foreman.user }}"
password: "{{ foreman.password }}"
server_url: "{{ foreman.url }}"
organization: "{{ organization.key }}"
content_view: "{{ content_view }}"
when: highest_version | int >= environments.Library | int or publish_only | default(False)


# only promote environments defined in the vars
- name: "Promote Environments to Version N+1"
theforman.foreman.content_view_version:
username: "{{ foreman.user }}"
password: "{{ foreman.password }}"
server_url: "{{ foreman.url }}"
organization: "{{ organization.key }}"
content_view: "{{ content_view }}"
# dictionaries aren't order 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 and not publish_only | default(False)
57 changes: 57 additions & 0 deletions roles/content_view_promotion_rollback_publish/tasks/rollback.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
# 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.url }}"
organization: "{{ organization.key }}"
resource: content_views
search: name = "{{ content_view }}"
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.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


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


# 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.url }}"
organization: "{{ organization.key }}"
content_view: "{{ content_view }}"
# dictionaries aren't order 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
22 changes: 22 additions & 0 deletions roles/content_view_promotion_rollback_publish/vars/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
bmarlow marked this conversation as resolved.
Show resolved Hide resolved
# vars file for Satellite_Patch_Management
organizations:
Lab:
lifecycle_environments:
- "Dev"
- "Prod"
- "QA"
content_views:
- "Test1"
- "Test2"
# Test:
# lifecycle_environments:
# - "Foo"
# - "Bar"
# - "Baz"

# HERE FOR DATA EXAMPLE ONLY, DO NOT USE THIS FILE FOR STORING YOUR SATELLITE PASSWORD!
foreman:
user: "admin"
password: "changeme"
url: "https://myforemanserver.myorg.com"