diff --git a/README.md b/README.md index fc192b9..6e638e1 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,11 @@ variables from the file specified. Role Variables -------------- +* `generate_resources` - A flag to generate resources as json files + rather than provisioning them. Defaults to `False`. See + [additional notes on the generate_resources flag](#Additional-notes-on-the-generate_resources-flag) + for more details. + * `oc_cmd_base` - The base `oc` command. Defaults to "oc", but can be set to specify a different path or add custom options @@ -408,6 +413,21 @@ specified above for `projects[*].resources` with the addition that each entry here must specify `metadata.namespace` to specify the target project for the resource. +### Additional notes on the `generate_resources` flag + +* All files will be created in a directory named `manifests` in the format + `__.json` where: + * `scope` is "cluster" for a cluster resource, or the target namespace name + * `kind` is the Kubernetes resource kind + * `name` is the metadata name for the resource +* If a project does not exist, this role will still attempt to create the project. +* Modification actions to a resource requires the resource to exist so it can be + used for comparison. +* If service accounts, role bindings, and cluster role bindings are defined as + `service_accounts`, `role_bindings`, and `cluster_role_bindings` variables, + the role will attempt to apply them. If you want them to be generated, you + would have to define them as you would any other resource. + Example Playbook with Provisioning by Role Variables ---------------------------------------------------- diff --git a/defaults/main.yml b/defaults/main.yml index e118380..7023940 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -7,3 +7,5 @@ openshift_resource_path: openshift_provision_change_record: '' openshift_provision_cluster_vars: {} + +generate_resources: false diff --git a/library/openshift_provision.py b/library/openshift_provision.py index 7b7bce1..1a9cdc1 100644 --- a/library/openshift_provision.py +++ b/library/openshift_provision.py @@ -230,9 +230,12 @@ def compare_set_list(path, src, dst): return list(compare_values(['/' + field], current, config)) def set_dict_defaults(d, default): - for k, v in default.items(): - if k not in d: - d[k] = v + if d is None: + d = default.items() + else: + for k, v in default.items(): + if k not in d: + d[k] = v def normalize_cpu_units(cpu): cpu = str(cpu) @@ -302,7 +305,7 @@ def merge_dict(merged, patch, overwrite=True): def mark_list_is_set(lst, key_name=None): lst.append({ - '__special_list_type__': 'set' + '__special_list_type__': 'set' }) def list_is_set(lst): @@ -838,8 +841,8 @@ def normalize_PolicyRule_V1(rule): # to always be null? Remove for comparison. if 'attributeRestrictions' in rule and rule['attributeRestrictions'] == None: del rule['attributeRestrictions'] - - + + def normalize_Probe_V1(probe): if probe == None: @@ -1055,6 +1058,7 @@ def __init__(self, module): self.patch = None self.patch_type = module.params['patch_type'] self.resource = module.params['resource'] + self.generate_resources = module.params['generate_resources'] if not 'kind' in self.resource: raise Exception('resource must define kind') @@ -1393,13 +1397,15 @@ def provision(self): if self.action == 'create': if current_resource: self.resource = current_resource - return + if not self.generate_resources: + return elif self.action == 'apply': if current_resource != None: patch = self.compare_resource(current_resource) if not patch: self.resource = current_resource - return + if not self.generate_resources: + return # If current resource does not match last_applied_configuration # then we must switch to replace mode or risk unexpected behavior if( current_resource_version @@ -1413,7 +1419,8 @@ def provision(self): patch = self.check_patch(current_resource) if not patch: self.resource = current_resource - return + if not self.generate_resources: + return elif self.action == 'replace': if current_resource == None: self.action = 'create' @@ -1421,10 +1428,12 @@ def provision(self): patch = self.compare_resource(current_resource) if not patch: self.resource = current_resource - return + if not self.generate_resources: + return elif self.action == 'delete': if current_resource == None: - return + if not self.generate_resources: + return elif self.action == 'ignore': return @@ -1439,6 +1448,21 @@ def provision(self): if self.module.check_mode: return + if self.generate_resources: + if not self.namespace: + scope = "cluster" + else: + scope = self.namespace + + if not os.path.exists('./manifests'): + os.mkdir('./manifests') + + resource_filename = "%s_%s_%s.json" % (scope, self.resource['kind'], self.resource['metadata']['name']) + resource_file = open("./manifests/" + resource_filename, 'w') + resource_file.write(str(json.dumps(self.resource))) + resource_file.close() + return + # Perform action on resource if self.action == 'delete': command = ['delete', self.resource['kind'], self.resource['metadata']['name']] @@ -1495,6 +1519,12 @@ def run_module(): 'fail_on_change': { 'type': 'bool', 'default': False + }, + # Use role as resource generator instead of provisioner + 'generate_resources': { + 'type': 'bool', + 'required': False, + 'default': False } } diff --git a/tasks/cluster-resources.yml b/tasks/cluster-resources.yml index cf1a9f4..8b3353a 100644 --- a/tasks/cluster-resources.yml +++ b/tasks/cluster-resources.yml @@ -11,6 +11,7 @@ connection: oc_cmd: "{{ oc_cmd }}" resource: "{{ resource }}" + generate_resources: "{{ generate_resources }}" with_items: "{{ resource_list }}" loop_control: loop_var: resource diff --git a/tasks/group.yml b/tasks/group.yml index d112255..af7a477 100644 --- a/tasks/group.yml +++ b/tasks/group.yml @@ -13,6 +13,7 @@ action: apply connection: oc_cmd: "{{ oc_cmd }}" + generate_resources: "{{ generate_resources }}" resource: apiVersion: v1 kind: Group diff --git a/tasks/helm-chart.yml b/tasks/helm-chart.yml index 6a5f5b7..ce13ee4 100644 --- a/tasks/helm-chart.yml +++ b/tasks/helm-chart.yml @@ -39,7 +39,7 @@ - name: Fetch helm chart command: >- - helm fetch + helm fetch {% if 'ca_file' in helm_chart.fetch %}--ca-file {{ helm_chart.fetch.ca_file | quote }}{% endif %} {% if 'cert_file' in helm_chart.fetch %}--cert-file {{ helm_chart.fetch.cert_file | quote }}{% endif %} {% if helm_chart.fetch.devel | default(False) | bool %}--devel{% endif %} @@ -87,6 +87,7 @@ oc_cmd: "{{ oc_cmd }}" namespace: "{{ project.name if project is defined else '' }}" resource: "{{ item }}" + generate_resources: "{{ generate_resources }}" with_items: "{{ process_helm_template.stdout | yaml_to_resource_list }}" vars: provision_action: >- diff --git a/tasks/process-template.yml b/tasks/process-template.yml index df74c97..4b1502c 100644 --- a/tasks/process-template.yml +++ b/tasks/process-template.yml @@ -80,6 +80,7 @@ oc_cmd: "{{ oc_cmd }}" namespace: "{{ project.name if project is defined else '' }}" resource: "{{ item }}" + generate_resources: "{{ generate_resources }}" with_items: >- {{ process_template.stdout | from_json | json_query('items') }} vars: diff --git a/tasks/project-resources.yml b/tasks/project-resources.yml index 4a464d7..779f5fc 100644 --- a/tasks/project-resources.yml +++ b/tasks/project-resources.yml @@ -12,6 +12,7 @@ oc_cmd: "{{ oc_cmd }}" namespace: "{{ project.name }}" resource: "{{ resource }}" + generate_resources: "{{ generate_resources }}" with_items: "{{ resource_list }}" loop_control: loop_var: resource diff --git a/tests/test-openshift_provision-generate_resource.yml b/tests/test-openshift_provision-generate_resource.yml new file mode 100644 index 0000000..71d07f9 --- /dev/null +++ b/tests/test-openshift_provision-generate_resource.yml @@ -0,0 +1,66 @@ +--- +- name: Set Facts + hosts: localhost + connection: local + vars_files: + - login-creds.yml + tasks: + - include_tasks: setup-test.yml + - set_fact: + ignore_differences: + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: '' + creationTimestamp: null + selfLink: null + namespace: provision-test + spec: + sessionAffinityConfig: + clientIP: + timeoutSeconds: 10800 + +- name: Test Provision + hosts: localhost + connection: local + vars: + provision_service: >- + {{ lookup('template', 'resources/test-service.yml.j2') + | from_yaml }} + service_ports: + - name: 8080-tcp + port: 8080 + + roles: + - role: openshift-provision + openshift_clusters: + - projects: + - name: provision-test + + tasks: + - name: Provision Service + openshift_provision: + connection: "{{ openshift_connection }}" + namespace: provision-test + resource: "{{ provision_service }}" + generate_resources: True + + - name: Get Service + command: cat ./manifests/provision-test_Service_test-service.json + register: get_service + changed_when: false + + - name: Verify Service + fail: + msg: | + Service not defined as expected + >>> + {{ cmp_service | to_yaml }} + === + {{ got_service | to_yaml }} + <<< + vars: + got_service: "{{ get_service.stdout | from_json | combine(ignore_differences, recursive=True) }}" + cmp_service: "{{ provision_service | combine(ignore_differences, recursive=True) }}" + when: >- + cmp_service.metadata != got_service.metadata or + cmp_service.spec != got_service.spec