From e538d1a4a43b0c675347598510a1307bef5f38b9 Mon Sep 17 00:00:00 2001 From: bmarlow Date: Mon, 3 May 2021 14:15:41 -0700 Subject: [PATCH 1/3] create content_view_rollback role --- roles/content_view_rollback/README.md | 108 ++++++++++++++++++ roles/content_view_rollback/defaults/main.yml | 3 + .../tasks/content-view.yml | 6 + roles/content_view_rollback/tasks/main.yml | 6 + .../content_view_rollback/tasks/rollback.yml | 90 +++++++++++++++ roles/content_view_rollback/tasks/version.yml | 8 ++ 6 files changed, 221 insertions(+) create mode 100644 roles/content_view_rollback/README.md create mode 100644 roles/content_view_rollback/defaults/main.yml create mode 100644 roles/content_view_rollback/tasks/content-view.yml create mode 100644 roles/content_view_rollback/tasks/main.yml create mode 100644 roles/content_view_rollback/tasks/rollback.yml create mode 100644 roles/content_view_rollback/tasks/version.yml diff --git a/roles/content_view_rollback/README.md b/roles/content_view_rollback/README.md new file mode 100644 index 0000000000..4ed8726a3d --- /dev/null +++ b/roles/content_view_rollback/README.md @@ -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" +``` diff --git a/roles/content_view_rollback/defaults/main.yml b/roles/content_view_rollback/defaults/main.yml new file mode 100644 index 0000000000..60001f2a0b --- /dev/null +++ b/roles/content_view_rollback/defaults/main.yml @@ -0,0 +1,3 @@ +--- +# defaults file for content_view_promotion_rollback_publish +FOREMAN_VALIDATE_CERTS: true diff --git a/roles/content_view_rollback/tasks/content-view.yml b/roles/content_view_rollback/tasks/content-view.yml new file mode 100644 index 0000000000..7c6ce84681 --- /dev/null +++ b/roles/content_view_rollback/tasks/content-view.yml @@ -0,0 +1,6 @@ +--- +- name: "Rollback Content-Views" + include_tasks: rollback.yml + loop: "{{ organization.value.content_views }}" + loop_control: + loop_var: content_view diff --git a/roles/content_view_rollback/tasks/main.yml b/roles/content_view_rollback/tasks/main.yml new file mode 100644 index 0000000000..076faf85e1 --- /dev/null +++ b/roles/content_view_rollback/tasks/main.yml @@ -0,0 +1,6 @@ +--- +- name: "Run Content-View Role" + include_tasks: content-view.yml + with_dict: "{{ organizations }}" + loop_control: + loop_var: organization diff --git a/roles/content_view_rollback/tasks/rollback.yml b/roles/content_view_rollback/tasks/rollback.yml new file mode 100644 index 0000000000..dccaad8496 --- /dev/null +++ b/roles/content_view_rollback/tasks/rollback.yml @@ -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 diff --git a/roles/content_view_rollback/tasks/version.yml b/roles/content_view_rollback/tasks/version.yml new file mode 100644 index 0000000000..da1baa33c8 --- /dev/null +++ b/roles/content_view_rollback/tasks/version.yml @@ -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 From e33087c26fdaa58ed02143b279dbe8e5c47c99b9 Mon Sep 17 00:00:00 2001 From: bmarlow Date: Fri, 30 Jul 2021 14:18:08 -0700 Subject: [PATCH 2/3] refactor, remove organization level options --- .../tasks/content-view.yml | 6 -- roles/content_view_rollback/tasks/main.yml | 94 ++++++++++++++++++- .../content_view_rollback/tasks/rollback.yml | 90 ------------------ 3 files changed, 90 insertions(+), 100 deletions(-) delete mode 100644 roles/content_view_rollback/tasks/content-view.yml delete mode 100644 roles/content_view_rollback/tasks/rollback.yml diff --git a/roles/content_view_rollback/tasks/content-view.yml b/roles/content_view_rollback/tasks/content-view.yml deleted file mode 100644 index 7c6ce84681..0000000000 --- a/roles/content_view_rollback/tasks/content-view.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -- name: "Rollback Content-Views" - include_tasks: rollback.yml - loop: "{{ organization.value.content_views }}" - loop_control: - loop_var: content_view diff --git a/roles/content_view_rollback/tasks/main.yml b/roles/content_view_rollback/tasks/main.yml index 076faf85e1..5b26b3eac2 100644 --- a/roles/content_view_rollback/tasks/main.yml +++ b/roles/content_view_rollback/tasks/main.yml @@ -1,6 +1,92 @@ --- -- name: "Run Content-View Role" - include_tasks: content-view.yml - with_dict: "{{ organizations }}" +# 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: "{{ foreman_organization }}" + validate_certs: "{{ foreman_validate_certs }}" + resource: content_views + search: name = "{{ foreman_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_server_url }}" + organization: "{{ foreman_organization }}" + validate_certs: "{{ foreman_validate_certs }}" + 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 Decremented 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 | default({}) }}" loop_control: - loop_var: organization + 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: "{{ foreman_organization }}" + validate_certs: "{{ foreman_validate_certs }}" + content_view: "{{ foreman_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 foreman_lifecycle_environments diff --git a/roles/content_view_rollback/tasks/rollback.yml b/roles/content_view_rollback/tasks/rollback.yml deleted file mode 100644 index dccaad8496..0000000000 --- a/roles/content_view_rollback/tasks/rollback.yml +++ /dev/null @@ -1,90 +0,0 @@ ---- -# 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 From d1eb7b8e29729aa0ab2f322f969eeba15acd909a Mon Sep 17 00:00:00 2001 From: bmarlow Date: Fri, 30 Jul 2021 14:18:27 -0700 Subject: [PATCH 3/3] updated readme --- roles/content_view_rollback/README.md | 82 +++++++-------------------- 1 file changed, 22 insertions(+), 60 deletions(-) diff --git a/roles/content_view_rollback/README.md b/roles/content_view_rollback/README.md index 4ed8726a3d..d2ee73625b 100644 --- a/roles/content_view_rollback/README.md +++ b/roles/content_view_rollback/README.md @@ -11,44 +11,27 @@ 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 role requires most of the common foreman variable more noteably: + +`foreman_organization`: The Organization that the Content-View belongs to. + +`foreman_username`: A foreman user that has access rights to publish new Content-View versions in the aforementioned Organization. + +`foreman_password`: The password for the user. foreman_server_url: The URL used to access foreman. + + +As well as two additional variables: + +`foreman_content_view`: The name of the Content-View which should have Lifecycle Environments promoted. + +`foreman_lifecycle_environments`: A list of Lifecycle Environments that should be promtoed. -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. +You need a Foreman user with admin access to the Organization, Lifecycle_Environment, and Content_View 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. @@ -75,34 +58,13 @@ 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" +foreman_organization: "Org1" +foreman_content_view: "content-view1" +foreman_lifecycle_environments: + - "Dev" + - "QA" + - "Prod" + ``` 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" -```