2020-03-09 10:11:07 +01:00
|
|
|
#!/usr/bin/python
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2022-08-05 12:28:29 +02:00
|
|
|
# Copyright (c) 2019, Guillaume Martinez (lunik@tiwabbit.fr)
|
|
|
|
# Copyright (c) 2015, Werner Dijkerman (ikben@werner-dijkerman.nl)
|
|
|
|
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
2020-03-09 10:11:07 +01:00
|
|
|
|
|
|
|
from __future__ import absolute_import, division, print_function
|
|
|
|
__metaclass__ = type
|
|
|
|
|
2020-07-14 16:47:46 +02:00
|
|
|
DOCUMENTATION = r'''
|
2020-03-09 10:11:07 +01:00
|
|
|
---
|
|
|
|
module: gitlab_project
|
|
|
|
short_description: Creates/updates/deletes GitLab Projects
|
|
|
|
description:
|
|
|
|
- When the project does not exist in GitLab, it will be created.
|
2023-06-15 15:46:44 +02:00
|
|
|
- When the project does exists and O(state=absent), the project will be deleted.
|
2020-03-09 10:11:07 +01:00
|
|
|
- When changes are made to the project, the project will be updated.
|
|
|
|
author:
|
|
|
|
- Werner Dijkerman (@dj-wasabi)
|
|
|
|
- Guillaume Martinez (@Lunik)
|
|
|
|
requirements:
|
|
|
|
- python-gitlab python module
|
|
|
|
extends_documentation_fragment:
|
2021-12-20 21:59:12 +01:00
|
|
|
- community.general.auth_basic
|
|
|
|
- community.general.gitlab
|
2023-02-20 17:26:28 +01:00
|
|
|
- community.general.attributes
|
|
|
|
|
|
|
|
attributes:
|
|
|
|
check_mode:
|
|
|
|
support: full
|
|
|
|
diff_mode:
|
|
|
|
support: none
|
2020-03-09 10:11:07 +01:00
|
|
|
|
|
|
|
options:
|
|
|
|
group:
|
|
|
|
description:
|
2020-12-27 10:55:06 +01:00
|
|
|
- Id or the full path of the group of which this projects belongs to.
|
2020-03-09 10:11:07 +01:00
|
|
|
type: str
|
|
|
|
name:
|
|
|
|
description:
|
2020-12-27 10:55:06 +01:00
|
|
|
- The name of the project.
|
2020-03-09 10:11:07 +01:00
|
|
|
required: true
|
|
|
|
type: str
|
|
|
|
path:
|
|
|
|
description:
|
|
|
|
- The path of the project you want to create, this will be server_url/<group>/path.
|
|
|
|
- If not supplied, name will be used.
|
|
|
|
type: str
|
|
|
|
description:
|
|
|
|
description:
|
|
|
|
- An description for the project.
|
|
|
|
type: str
|
2021-10-30 08:14:30 +02:00
|
|
|
initialize_with_readme:
|
|
|
|
description:
|
|
|
|
- Will initialize the project with a default C(README.md).
|
|
|
|
- Is only used when the project is created, and ignored otherwise.
|
|
|
|
type: bool
|
|
|
|
default: false
|
|
|
|
version_added: "4.0.0"
|
2020-03-09 10:11:07 +01:00
|
|
|
issues_enabled:
|
|
|
|
description:
|
|
|
|
- Whether you want to create issues or not.
|
|
|
|
- Possible values are true and false.
|
|
|
|
type: bool
|
2022-08-24 19:59:56 +02:00
|
|
|
default: true
|
2020-03-09 10:11:07 +01:00
|
|
|
merge_requests_enabled:
|
|
|
|
description:
|
|
|
|
- If merge requests can be made or not.
|
|
|
|
- Possible values are true and false.
|
|
|
|
type: bool
|
2022-08-24 19:59:56 +02:00
|
|
|
default: true
|
2020-03-09 10:11:07 +01:00
|
|
|
wiki_enabled:
|
|
|
|
description:
|
|
|
|
- If an wiki for this project should be available or not.
|
|
|
|
type: bool
|
2022-08-24 19:59:56 +02:00
|
|
|
default: true
|
2020-03-09 10:11:07 +01:00
|
|
|
snippets_enabled:
|
|
|
|
description:
|
|
|
|
- If creating snippets should be available or not.
|
|
|
|
type: bool
|
2022-08-24 19:59:56 +02:00
|
|
|
default: true
|
2020-03-09 10:11:07 +01:00
|
|
|
visibility:
|
|
|
|
description:
|
2023-06-15 15:46:44 +02:00
|
|
|
- V(private) Project access must be granted explicitly for each user.
|
|
|
|
- V(internal) The project can be cloned by any logged in user.
|
|
|
|
- V(public) The project can be cloned without any authentication.
|
2020-03-09 10:11:07 +01:00
|
|
|
default: private
|
|
|
|
type: str
|
|
|
|
choices: ["private", "internal", "public"]
|
|
|
|
aliases:
|
|
|
|
- visibility_level
|
|
|
|
import_url:
|
|
|
|
description:
|
|
|
|
- Git repository which will be imported into gitlab.
|
|
|
|
- GitLab server needs read access to this git repository.
|
|
|
|
required: false
|
|
|
|
type: str
|
|
|
|
state:
|
|
|
|
description:
|
2020-12-27 10:55:06 +01:00
|
|
|
- Create or delete project.
|
2020-03-09 10:11:07 +01:00
|
|
|
- Possible values are present and absent.
|
|
|
|
default: present
|
|
|
|
type: str
|
|
|
|
choices: ["present", "absent"]
|
2020-07-14 16:47:46 +02:00
|
|
|
merge_method:
|
|
|
|
description:
|
|
|
|
- What requirements are placed upon merges.
|
2023-06-15 15:46:44 +02:00
|
|
|
- Possible values are V(merge), V(rebase_merge) merge commit with semi-linear history, V(ff) fast-forward merges only.
|
2020-07-14 16:47:46 +02:00
|
|
|
type: str
|
|
|
|
choices: ["ff", "merge", "rebase_merge"]
|
|
|
|
default: merge
|
|
|
|
version_added: "1.0.0"
|
2020-12-27 10:55:06 +01:00
|
|
|
lfs_enabled:
|
|
|
|
description:
|
|
|
|
- Enable Git large file systems to manages large files such
|
|
|
|
as audio, video, and graphics files.
|
|
|
|
type: bool
|
|
|
|
required: false
|
|
|
|
default: false
|
|
|
|
version_added: "2.0.0"
|
2021-06-27 16:09:41 +02:00
|
|
|
username:
|
|
|
|
description:
|
|
|
|
- Used to create a personal project under a user's name.
|
|
|
|
type: str
|
|
|
|
version_added: "3.3.0"
|
2021-07-19 11:52:32 +02:00
|
|
|
allow_merge_on_skipped_pipeline:
|
|
|
|
description:
|
|
|
|
- Allow merge when skipped pipelines exist.
|
|
|
|
type: bool
|
|
|
|
version_added: "3.4.0"
|
|
|
|
only_allow_merge_if_all_discussions_are_resolved:
|
|
|
|
description:
|
|
|
|
- All discussions on a merge request (MR) have to be resolved.
|
|
|
|
type: bool
|
|
|
|
version_added: "3.4.0"
|
|
|
|
only_allow_merge_if_pipeline_succeeds:
|
|
|
|
description:
|
|
|
|
- Only allow merges if pipeline succeeded.
|
|
|
|
type: bool
|
|
|
|
version_added: "3.4.0"
|
|
|
|
packages_enabled:
|
|
|
|
description:
|
|
|
|
- Enable GitLab package repository.
|
|
|
|
type: bool
|
|
|
|
version_added: "3.4.0"
|
|
|
|
remove_source_branch_after_merge:
|
|
|
|
description:
|
|
|
|
- Remove the source branch after merge.
|
|
|
|
type: bool
|
|
|
|
version_added: "3.4.0"
|
|
|
|
squash_option:
|
|
|
|
description:
|
|
|
|
- Squash commits when merging.
|
|
|
|
type: str
|
|
|
|
choices: ["never", "always", "default_off", "default_on"]
|
|
|
|
version_added: "3.4.0"
|
2021-09-20 06:54:43 +02:00
|
|
|
ci_config_path:
|
|
|
|
description:
|
|
|
|
- Custom path to the CI configuration file for this project.
|
|
|
|
type: str
|
|
|
|
version_added: "3.7.0"
|
|
|
|
shared_runners_enabled:
|
|
|
|
description:
|
|
|
|
- Enable shared runners for this project.
|
|
|
|
type: bool
|
|
|
|
version_added: "3.7.0"
|
2021-11-30 06:12:28 +01:00
|
|
|
avatar_path:
|
|
|
|
description:
|
|
|
|
- Absolute path image to configure avatar. File size should not exceed 200 kb.
|
|
|
|
- This option is only used on creation, not for updates.
|
|
|
|
type: path
|
|
|
|
version_added: "4.2.0"
|
|
|
|
default_branch:
|
|
|
|
description:
|
2023-09-06 19:11:11 +02:00
|
|
|
- The default branch name for this project.
|
|
|
|
- For project creation, this option requires O(initialize_with_readme=true).
|
|
|
|
- For project update, the branch must exist.
|
|
|
|
- Supports project's default branch update since community.general 8.0.0.
|
2021-11-30 06:12:28 +01:00
|
|
|
type: str
|
|
|
|
version_added: "4.2.0"
|
2024-08-11 20:21:49 +02:00
|
|
|
repository_access_level:
|
|
|
|
description:
|
|
|
|
- V(private) means that accessing repository is allowed only to project members.
|
|
|
|
- V(disabled) means that accessing repository is disabled.
|
|
|
|
- V(enabled) means that accessing repository is enabled.
|
|
|
|
type: str
|
|
|
|
choices: ["private", "disabled", "enabled"]
|
|
|
|
version_added: "9.3.0"
|
2023-01-12 21:06:52 +01:00
|
|
|
builds_access_level:
|
|
|
|
description:
|
2023-06-15 15:46:44 +02:00
|
|
|
- V(private) means that repository CI/CD is allowed only to project members.
|
|
|
|
- V(disabled) means that repository CI/CD is disabled.
|
|
|
|
- V(enabled) means that repository CI/CD is enabled.
|
2023-01-12 21:06:52 +01:00
|
|
|
type: str
|
|
|
|
choices: ["private", "disabled", "enabled"]
|
|
|
|
version_added: "6.2.0"
|
|
|
|
forking_access_level:
|
|
|
|
description:
|
2023-06-15 15:46:44 +02:00
|
|
|
- V(private) means that repository forks is allowed only to project members.
|
|
|
|
- V(disabled) means that repository forks are disabled.
|
|
|
|
- V(enabled) means that repository forks are enabled.
|
2023-01-12 21:06:52 +01:00
|
|
|
type: str
|
|
|
|
choices: ["private", "disabled", "enabled"]
|
|
|
|
version_added: "6.2.0"
|
|
|
|
container_registry_access_level:
|
|
|
|
description:
|
2023-06-15 15:46:44 +02:00
|
|
|
- V(private) means that container registry is allowed only to project members.
|
|
|
|
- V(disabled) means that container registry is disabled.
|
|
|
|
- V(enabled) means that container registry is enabled.
|
2023-01-12 21:06:52 +01:00
|
|
|
type: str
|
|
|
|
choices: ["private", "disabled", "enabled"]
|
|
|
|
version_added: "6.2.0"
|
2023-02-26 14:13:22 +01:00
|
|
|
releases_access_level:
|
|
|
|
description:
|
2023-06-15 15:46:44 +02:00
|
|
|
- V(private) means that accessing release is allowed only to project members.
|
|
|
|
- V(disabled) means that accessing release is disabled.
|
|
|
|
- V(enabled) means that accessing release is enabled.
|
2023-02-26 14:13:22 +01:00
|
|
|
type: str
|
|
|
|
choices: ["private", "disabled", "enabled"]
|
|
|
|
version_added: "6.4.0"
|
|
|
|
environments_access_level:
|
|
|
|
description:
|
2023-06-15 15:46:44 +02:00
|
|
|
- V(private) means that deployment to environment is allowed only to project members.
|
|
|
|
- V(disabled) means that deployment to environment is disabled.
|
|
|
|
- V(enabled) means that deployment to environment is enabled.
|
2023-02-26 14:13:22 +01:00
|
|
|
type: str
|
|
|
|
choices: ["private", "disabled", "enabled"]
|
|
|
|
version_added: "6.4.0"
|
|
|
|
feature_flags_access_level:
|
|
|
|
description:
|
2023-06-15 15:46:44 +02:00
|
|
|
- V(private) means that feature rollout is allowed only to project members.
|
|
|
|
- V(disabled) means that feature rollout is disabled.
|
|
|
|
- V(enabled) means that feature rollout is enabled.
|
2023-02-26 14:13:22 +01:00
|
|
|
type: str
|
|
|
|
choices: ["private", "disabled", "enabled"]
|
|
|
|
version_added: "6.4.0"
|
|
|
|
infrastructure_access_level:
|
|
|
|
description:
|
2023-06-15 15:46:44 +02:00
|
|
|
- V(private) means that configuring infrastructure is allowed only to project members.
|
|
|
|
- V(disabled) means that configuring infrastructure is disabled.
|
|
|
|
- V(enabled) means that configuring infrastructure is enabled.
|
2023-02-26 14:13:22 +01:00
|
|
|
type: str
|
|
|
|
choices: ["private", "disabled", "enabled"]
|
|
|
|
version_added: "6.4.0"
|
|
|
|
monitor_access_level:
|
|
|
|
description:
|
2023-06-15 15:46:44 +02:00
|
|
|
- V(private) means that monitoring health is allowed only to project members.
|
|
|
|
- V(disabled) means that monitoring health is disabled.
|
|
|
|
- V(enabled) means that monitoring health is enabled.
|
2023-02-26 14:13:22 +01:00
|
|
|
type: str
|
|
|
|
choices: ["private", "disabled", "enabled"]
|
|
|
|
version_added: "6.4.0"
|
|
|
|
security_and_compliance_access_level:
|
|
|
|
description:
|
2023-06-15 15:46:44 +02:00
|
|
|
- V(private) means that accessing security and complicance tab is allowed only to project members.
|
|
|
|
- V(disabled) means that accessing security and complicance tab is disabled.
|
|
|
|
- V(enabled) means that accessing security and complicance tab is enabled.
|
2023-02-26 14:13:22 +01:00
|
|
|
type: str
|
|
|
|
choices: ["private", "disabled", "enabled"]
|
|
|
|
version_added: "6.4.0"
|
2023-04-03 22:02:20 +02:00
|
|
|
topics:
|
|
|
|
description:
|
|
|
|
- A topic or list of topics to be assigned to a project.
|
|
|
|
- It is compatible with old GitLab server releases (versions before 14, correspond to C(tag_list)).
|
|
|
|
type: list
|
|
|
|
elements: str
|
|
|
|
version_added: "6.6.0"
|
2024-08-11 20:21:49 +02:00
|
|
|
container_expiration_policy:
|
|
|
|
description:
|
|
|
|
- Project cleanup policy for its container registry.
|
|
|
|
type: dict
|
|
|
|
suboptions:
|
|
|
|
cadence:
|
|
|
|
description:
|
|
|
|
- How often cleanup should be run.
|
|
|
|
type: str
|
|
|
|
choices: ["1d", "7d", "14d", "1month", "3month"]
|
|
|
|
enabled:
|
|
|
|
description:
|
|
|
|
- Enable the cleanup policy.
|
|
|
|
type: bool
|
|
|
|
keep_n:
|
|
|
|
description:
|
|
|
|
- Number of tags kept per image name.
|
|
|
|
- V(0) clears the field.
|
|
|
|
type: int
|
|
|
|
choices: [0, 1, 5, 10, 25, 50, 100]
|
|
|
|
older_than:
|
|
|
|
description:
|
|
|
|
- Destroy tags older than this.
|
|
|
|
- V(0d) clears the field.
|
|
|
|
type: str
|
|
|
|
choices: ["0d", "7d", "14d", "30d", "90d"]
|
|
|
|
name_regex:
|
|
|
|
description:
|
|
|
|
- Destroy tags matching this regular expression.
|
|
|
|
type: str
|
|
|
|
name_regex_keep:
|
|
|
|
description:
|
|
|
|
- Keep tags matching this regular expression.
|
|
|
|
type: str
|
|
|
|
version_added: "9.3.0"
|
2020-03-09 10:11:07 +01:00
|
|
|
'''
|
|
|
|
|
2020-07-14 16:47:46 +02:00
|
|
|
EXAMPLES = r'''
|
2020-12-27 10:55:06 +01:00
|
|
|
- name: Create GitLab Project
|
|
|
|
community.general.gitlab_project:
|
|
|
|
api_url: https://gitlab.example.com/
|
|
|
|
api_token: "{{ api_token }}"
|
|
|
|
name: my_first_project
|
|
|
|
group: "10481470"
|
|
|
|
|
2020-03-09 10:11:07 +01:00
|
|
|
- name: Delete GitLab Project
|
2020-07-13 21:50:31 +02:00
|
|
|
community.general.gitlab_project:
|
2020-03-09 10:11:07 +01:00
|
|
|
api_url: https://gitlab.example.com/
|
|
|
|
api_token: "{{ access_token }}"
|
|
|
|
name: my_first_project
|
|
|
|
state: absent
|
|
|
|
delegate_to: localhost
|
|
|
|
|
|
|
|
- name: Create GitLab Project in group Ansible
|
2020-07-13 21:50:31 +02:00
|
|
|
community.general.gitlab_project:
|
2020-03-09 10:11:07 +01:00
|
|
|
api_url: https://gitlab.example.com/
|
2022-09-06 20:42:17 +02:00
|
|
|
validate_certs: true
|
2020-03-09 10:11:07 +01:00
|
|
|
api_username: dj-wasabi
|
|
|
|
api_password: "MySecretPassword"
|
|
|
|
name: my_first_project
|
|
|
|
group: ansible
|
2022-09-06 20:42:17 +02:00
|
|
|
issues_enabled: false
|
2020-07-14 16:47:46 +02:00
|
|
|
merge_method: rebase_merge
|
2022-09-06 20:42:17 +02:00
|
|
|
wiki_enabled: true
|
|
|
|
snippets_enabled: true
|
2020-03-09 10:11:07 +01:00
|
|
|
import_url: http://git.example.com/example/lab.git
|
2021-10-30 08:14:30 +02:00
|
|
|
initialize_with_readme: true
|
2020-03-09 10:11:07 +01:00
|
|
|
state: present
|
|
|
|
delegate_to: localhost
|
2021-12-20 21:59:12 +01:00
|
|
|
|
|
|
|
- name: get the initial root password
|
|
|
|
ansible.builtin.shell: |
|
|
|
|
grep 'Password:' /etc/gitlab/initial_root_password | sed -e 's/Password\: \(.*\)/\1/'
|
|
|
|
register: initial_root_password
|
|
|
|
|
|
|
|
- name: Create a GitLab Project using a username/password via oauth_token
|
|
|
|
community.general.gitlab_project:
|
|
|
|
api_url: https://gitlab.example.com/
|
|
|
|
api_username: root
|
|
|
|
api_password: "{{ initial_root_password }}"
|
|
|
|
name: my_second_project
|
|
|
|
group: "10481470"
|
2020-03-09 10:11:07 +01:00
|
|
|
'''
|
|
|
|
|
2020-07-14 16:47:46 +02:00
|
|
|
RETURN = r'''
|
2020-03-09 10:11:07 +01:00
|
|
|
msg:
|
2020-07-14 16:47:46 +02:00
|
|
|
description: Success or failure message.
|
2020-03-09 10:11:07 +01:00
|
|
|
returned: always
|
|
|
|
type: str
|
|
|
|
sample: "Success"
|
|
|
|
|
|
|
|
result:
|
2020-07-14 16:47:46 +02:00
|
|
|
description: json parsed response from the server.
|
2020-03-09 10:11:07 +01:00
|
|
|
returned: always
|
|
|
|
type: dict
|
|
|
|
|
|
|
|
error:
|
2020-07-14 16:47:46 +02:00
|
|
|
description: the error message returned by the GitLab API.
|
2020-03-09 10:11:07 +01:00
|
|
|
returned: failed
|
|
|
|
type: str
|
|
|
|
sample: "400: path is already in use"
|
|
|
|
|
|
|
|
project:
|
2020-07-14 16:47:46 +02:00
|
|
|
description: API object.
|
2020-03-09 10:11:07 +01:00
|
|
|
returned: always
|
|
|
|
type: dict
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
|
|
from ansible.module_utils.api import basic_auth_argument_spec
|
2022-09-12 20:30:02 +02:00
|
|
|
from ansible.module_utils.basic import AnsibleModule
|
2021-06-26 23:59:11 +02:00
|
|
|
from ansible.module_utils.common.text.converters import to_native
|
2020-03-09 10:11:07 +01:00
|
|
|
|
2022-09-12 20:30:02 +02:00
|
|
|
from ansible_collections.community.general.plugins.module_utils.gitlab import (
|
2023-11-11 12:20:12 +01:00
|
|
|
auth_argument_spec, find_group, find_project, gitlab_authentication, gitlab
|
2022-09-12 20:30:02 +02:00
|
|
|
)
|
2020-03-09 10:11:07 +01:00
|
|
|
|
2023-04-03 22:02:20 +02:00
|
|
|
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
|
|
|
|
|
2020-03-09 10:11:07 +01:00
|
|
|
|
|
|
|
class GitLabProject(object):
|
|
|
|
def __init__(self, module, gitlab_instance):
|
|
|
|
self._module = module
|
|
|
|
self._gitlab = gitlab_instance
|
2021-11-16 13:01:32 +01:00
|
|
|
self.project_object = None
|
2020-03-09 10:11:07 +01:00
|
|
|
|
|
|
|
'''
|
|
|
|
@param project_name Name of the project
|
|
|
|
@param namespace Namespace Object (User or Group)
|
|
|
|
@param options Options of the project
|
|
|
|
'''
|
2023-09-06 19:11:11 +02:00
|
|
|
def create_or_update_project(self, module, project_name, namespace, options):
|
2020-03-09 10:11:07 +01:00
|
|
|
changed = False
|
2020-12-27 10:55:06 +01:00
|
|
|
project_options = {
|
|
|
|
'name': project_name,
|
|
|
|
'description': options['description'],
|
|
|
|
'issues_enabled': options['issues_enabled'],
|
|
|
|
'merge_requests_enabled': options['merge_requests_enabled'],
|
|
|
|
'merge_method': options['merge_method'],
|
|
|
|
'wiki_enabled': options['wiki_enabled'],
|
|
|
|
'snippets_enabled': options['snippets_enabled'],
|
|
|
|
'visibility': options['visibility'],
|
|
|
|
'lfs_enabled': options['lfs_enabled'],
|
2021-07-19 11:52:32 +02:00
|
|
|
'allow_merge_on_skipped_pipeline': options['allow_merge_on_skipped_pipeline'],
|
|
|
|
'only_allow_merge_if_all_discussions_are_resolved': options['only_allow_merge_if_all_discussions_are_resolved'],
|
|
|
|
'only_allow_merge_if_pipeline_succeeds': options['only_allow_merge_if_pipeline_succeeds'],
|
|
|
|
'packages_enabled': options['packages_enabled'],
|
|
|
|
'remove_source_branch_after_merge': options['remove_source_branch_after_merge'],
|
|
|
|
'squash_option': options['squash_option'],
|
2021-09-20 06:54:43 +02:00
|
|
|
'ci_config_path': options['ci_config_path'],
|
|
|
|
'shared_runners_enabled': options['shared_runners_enabled'],
|
2024-08-11 20:21:49 +02:00
|
|
|
'repository_access_level': options['repository_access_level'],
|
2023-01-12 21:06:52 +01:00
|
|
|
'builds_access_level': options['builds_access_level'],
|
|
|
|
'forking_access_level': options['forking_access_level'],
|
|
|
|
'container_registry_access_level': options['container_registry_access_level'],
|
2023-02-26 14:13:22 +01:00
|
|
|
'releases_access_level': options['releases_access_level'],
|
|
|
|
'environments_access_level': options['environments_access_level'],
|
|
|
|
'feature_flags_access_level': options['feature_flags_access_level'],
|
|
|
|
'infrastructure_access_level': options['infrastructure_access_level'],
|
|
|
|
'monitor_access_level': options['monitor_access_level'],
|
|
|
|
'security_and_compliance_access_level': options['security_and_compliance_access_level'],
|
2024-08-11 20:21:49 +02:00
|
|
|
'container_expiration_policy': options['container_expiration_policy'],
|
2020-12-27 10:55:06 +01:00
|
|
|
}
|
2023-04-03 22:02:20 +02:00
|
|
|
|
|
|
|
# topics was introduced on gitlab >=14 and replace tag_list. We get current gitlab version
|
|
|
|
# and check if less than 14. If yes we use tag_list instead topics
|
|
|
|
if LooseVersion(self._gitlab.version()[0]) < LooseVersion("14"):
|
|
|
|
project_options['tag_list'] = options['topics']
|
|
|
|
else:
|
|
|
|
project_options['topics'] = options['topics']
|
|
|
|
|
2020-03-09 10:11:07 +01:00
|
|
|
# Because we have already call userExists in main()
|
2021-11-16 13:01:32 +01:00
|
|
|
if self.project_object is None:
|
2023-09-06 19:11:11 +02:00
|
|
|
if options['default_branch'] and not options['initialize_with_readme']:
|
|
|
|
module.fail_json(msg="Param default_branch need param initialize_with_readme set to true")
|
2020-12-27 10:55:06 +01:00
|
|
|
project_options.update({
|
2020-03-09 10:11:07 +01:00
|
|
|
'path': options['path'],
|
2020-12-27 10:55:06 +01:00
|
|
|
'import_url': options['import_url'],
|
|
|
|
})
|
2021-10-30 08:14:30 +02:00
|
|
|
if options['initialize_with_readme']:
|
|
|
|
project_options['initialize_with_readme'] = options['initialize_with_readme']
|
2021-11-30 06:12:28 +01:00
|
|
|
if options['default_branch']:
|
|
|
|
project_options['default_branch'] = options['default_branch']
|
|
|
|
|
2021-11-16 13:01:32 +01:00
|
|
|
project_options = self.get_options_with_value(project_options)
|
|
|
|
project = self.create_project(namespace, project_options)
|
2021-11-30 06:12:28 +01:00
|
|
|
|
|
|
|
# add avatar to project
|
2021-12-20 20:00:49 +01:00
|
|
|
if options['avatar_path']:
|
|
|
|
try:
|
|
|
|
project.avatar = open(options['avatar_path'], 'rb')
|
|
|
|
except IOError as e:
|
|
|
|
self._module.fail_json(msg='Cannot open {0}: {1}'.format(options['avatar_path'], e))
|
2021-11-30 06:12:28 +01:00
|
|
|
|
2020-03-09 10:11:07 +01:00
|
|
|
changed = True
|
|
|
|
else:
|
2023-09-06 19:11:11 +02:00
|
|
|
if options['default_branch']:
|
|
|
|
project_options['default_branch'] = options['default_branch']
|
2021-11-16 13:01:32 +01:00
|
|
|
changed, project = self.update_project(self.project_object, project_options)
|
2020-03-09 10:11:07 +01:00
|
|
|
|
2021-11-16 13:01:32 +01:00
|
|
|
self.project_object = project
|
2020-03-09 10:11:07 +01:00
|
|
|
if changed:
|
|
|
|
if self._module.check_mode:
|
|
|
|
self._module.exit_json(changed=True, msg="Successfully created or updated the project %s" % project_name)
|
|
|
|
|
|
|
|
try:
|
|
|
|
project.save()
|
|
|
|
except Exception as e:
|
|
|
|
self._module.fail_json(msg="Failed update project: %s " % e)
|
|
|
|
return True
|
2020-12-27 10:55:06 +01:00
|
|
|
return False
|
2020-03-09 10:11:07 +01:00
|
|
|
|
|
|
|
'''
|
|
|
|
@param namespace Namespace Object (User or Group)
|
|
|
|
@param arguments Attributes of the project
|
|
|
|
'''
|
2021-11-16 13:01:32 +01:00
|
|
|
def create_project(self, namespace, arguments):
|
2020-03-09 10:11:07 +01:00
|
|
|
if self._module.check_mode:
|
|
|
|
return True
|
|
|
|
|
|
|
|
arguments['namespace_id'] = namespace.id
|
|
|
|
try:
|
|
|
|
project = self._gitlab.projects.create(arguments)
|
|
|
|
except (gitlab.exceptions.GitlabCreateError) as e:
|
|
|
|
self._module.fail_json(msg="Failed to create project: %s " % to_native(e))
|
|
|
|
|
|
|
|
return project
|
|
|
|
|
2021-07-19 11:52:32 +02:00
|
|
|
'''
|
|
|
|
@param arguments Attributes of the project
|
|
|
|
'''
|
2021-11-16 13:01:32 +01:00
|
|
|
def get_options_with_value(self, arguments):
|
2021-07-19 11:52:32 +02:00
|
|
|
ret_arguments = dict()
|
|
|
|
for arg_key, arg_value in arguments.items():
|
|
|
|
if arguments[arg_key] is not None:
|
|
|
|
ret_arguments[arg_key] = arg_value
|
|
|
|
|
|
|
|
return ret_arguments
|
|
|
|
|
2020-03-09 10:11:07 +01:00
|
|
|
'''
|
|
|
|
@param project Project Object
|
|
|
|
@param arguments Attributes of the project
|
|
|
|
'''
|
2021-11-16 13:01:32 +01:00
|
|
|
def update_project(self, project, arguments):
|
2020-03-09 10:11:07 +01:00
|
|
|
changed = False
|
|
|
|
|
|
|
|
for arg_key, arg_value in arguments.items():
|
|
|
|
if arguments[arg_key] is not None:
|
|
|
|
if getattr(project, arg_key) != arguments[arg_key]:
|
2024-08-11 20:21:49 +02:00
|
|
|
if arg_key == 'container_expiration_policy':
|
|
|
|
old_val = getattr(project, arg_key)
|
|
|
|
final_val = {key: value for key, value in arg_value.items() if value is not None}
|
|
|
|
|
|
|
|
if final_val.get('older_than') == '0d':
|
|
|
|
final_val['older_than'] = None
|
|
|
|
if final_val.get('keep_n') == 0:
|
|
|
|
final_val['keep_n'] = None
|
|
|
|
|
|
|
|
if all(old_val.get(key) == value for key, value in final_val.items()):
|
|
|
|
continue
|
|
|
|
setattr(project, 'container_expiration_policy_attributes', final_val)
|
|
|
|
else:
|
|
|
|
setattr(project, arg_key, arg_value)
|
2020-03-09 10:11:07 +01:00
|
|
|
changed = True
|
|
|
|
|
|
|
|
return (changed, project)
|
|
|
|
|
2021-11-16 13:01:32 +01:00
|
|
|
def delete_project(self):
|
2020-03-09 10:11:07 +01:00
|
|
|
if self._module.check_mode:
|
|
|
|
return True
|
|
|
|
|
2021-11-16 13:01:32 +01:00
|
|
|
project = self.project_object
|
2020-03-09 10:11:07 +01:00
|
|
|
|
|
|
|
return project.delete()
|
|
|
|
|
|
|
|
'''
|
|
|
|
@param namespace User/Group object
|
|
|
|
@param name Name of the project
|
|
|
|
'''
|
2021-11-16 13:01:32 +01:00
|
|
|
def exists_project(self, namespace, path):
|
|
|
|
# When project exists, object will be stored in self.project_object.
|
|
|
|
project = find_project(self._gitlab, namespace.full_path + '/' + path)
|
2020-03-09 10:11:07 +01:00
|
|
|
if project:
|
2021-11-16 13:01:32 +01:00
|
|
|
self.project_object = project
|
2020-03-09 10:11:07 +01:00
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
argument_spec = basic_auth_argument_spec()
|
2021-12-20 21:59:12 +01:00
|
|
|
argument_spec.update(auth_argument_spec())
|
2020-03-09 10:11:07 +01:00
|
|
|
argument_spec.update(dict(
|
|
|
|
group=dict(type='str'),
|
|
|
|
name=dict(type='str', required=True),
|
|
|
|
path=dict(type='str'),
|
|
|
|
description=dict(type='str'),
|
2021-10-30 08:14:30 +02:00
|
|
|
initialize_with_readme=dict(type='bool', default=False),
|
2021-11-30 06:12:28 +01:00
|
|
|
default_branch=dict(type='str'),
|
2020-03-09 10:11:07 +01:00
|
|
|
issues_enabled=dict(type='bool', default=True),
|
|
|
|
merge_requests_enabled=dict(type='bool', default=True),
|
2020-07-14 16:47:46 +02:00
|
|
|
merge_method=dict(type='str', default='merge', choices=["merge", "rebase_merge", "ff"]),
|
2020-03-09 10:11:07 +01:00
|
|
|
wiki_enabled=dict(type='bool', default=True),
|
|
|
|
snippets_enabled=dict(default=True, type='bool'),
|
|
|
|
visibility=dict(type='str', default="private", choices=["internal", "private", "public"], aliases=["visibility_level"]),
|
|
|
|
import_url=dict(type='str'),
|
|
|
|
state=dict(type='str', default="present", choices=["absent", "present"]),
|
2020-12-27 10:55:06 +01:00
|
|
|
lfs_enabled=dict(default=False, type='bool'),
|
2021-06-27 16:09:41 +02:00
|
|
|
username=dict(type='str'),
|
2021-07-19 11:52:32 +02:00
|
|
|
allow_merge_on_skipped_pipeline=dict(type='bool'),
|
|
|
|
only_allow_merge_if_all_discussions_are_resolved=dict(type='bool'),
|
|
|
|
only_allow_merge_if_pipeline_succeeds=dict(type='bool'),
|
|
|
|
packages_enabled=dict(type='bool'),
|
|
|
|
remove_source_branch_after_merge=dict(type='bool'),
|
|
|
|
squash_option=dict(type='str', choices=['never', 'always', 'default_off', 'default_on']),
|
2021-09-20 06:54:43 +02:00
|
|
|
ci_config_path=dict(type='str'),
|
|
|
|
shared_runners_enabled=dict(type='bool'),
|
2021-11-30 06:12:28 +01:00
|
|
|
avatar_path=dict(type='path'),
|
2024-08-11 20:21:49 +02:00
|
|
|
repository_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
2023-01-12 21:06:52 +01:00
|
|
|
builds_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
|
|
|
forking_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
|
|
|
container_registry_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
2023-02-26 14:13:22 +01:00
|
|
|
releases_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
|
|
|
environments_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
|
|
|
feature_flags_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
|
|
|
infrastructure_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
|
|
|
monitor_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
|
|
|
security_and_compliance_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
2023-04-03 22:02:20 +02:00
|
|
|
topics=dict(type='list', elements='str'),
|
2024-08-11 20:21:49 +02:00
|
|
|
container_expiration_policy=dict(type='dict', default=None, options=dict(
|
|
|
|
cadence=dict(type='str', choices=["1d", "7d", "14d", "1month", "3month"]),
|
|
|
|
enabled=dict(type='bool'),
|
|
|
|
keep_n=dict(type='int', choices=[0, 1, 5, 10, 25, 50, 100]),
|
|
|
|
older_than=dict(type='str', choices=["0d", "7d", "14d", "30d", "90d"]),
|
|
|
|
name_regex=dict(type='str'),
|
|
|
|
name_regex_keep=dict(type='str'),
|
|
|
|
)),
|
2020-03-09 10:11:07 +01:00
|
|
|
))
|
|
|
|
|
|
|
|
module = AnsibleModule(
|
|
|
|
argument_spec=argument_spec,
|
|
|
|
mutually_exclusive=[
|
|
|
|
['api_username', 'api_token'],
|
2021-12-20 21:59:12 +01:00
|
|
|
['api_username', 'api_oauth_token'],
|
|
|
|
['api_username', 'api_job_token'],
|
|
|
|
['api_token', 'api_oauth_token'],
|
|
|
|
['api_token', 'api_job_token'],
|
2021-06-27 16:09:41 +02:00
|
|
|
['group', 'username'],
|
2020-03-09 10:11:07 +01:00
|
|
|
],
|
|
|
|
required_together=[
|
|
|
|
['api_username', 'api_password'],
|
|
|
|
],
|
|
|
|
required_one_of=[
|
2021-12-20 21:59:12 +01:00
|
|
|
['api_username', 'api_token', 'api_oauth_token', 'api_job_token']
|
2020-03-09 10:11:07 +01:00
|
|
|
],
|
|
|
|
supports_check_mode=True,
|
|
|
|
)
|
2023-11-11 12:20:12 +01:00
|
|
|
|
|
|
|
# check prerequisites and connect to gitlab server
|
|
|
|
gitlab_instance = gitlab_authentication(module)
|
2020-03-09 10:11:07 +01:00
|
|
|
|
|
|
|
group_identifier = module.params['group']
|
|
|
|
project_name = module.params['name']
|
|
|
|
project_path = module.params['path']
|
|
|
|
project_description = module.params['description']
|
2021-10-30 08:14:30 +02:00
|
|
|
initialize_with_readme = module.params['initialize_with_readme']
|
2020-03-09 10:11:07 +01:00
|
|
|
issues_enabled = module.params['issues_enabled']
|
|
|
|
merge_requests_enabled = module.params['merge_requests_enabled']
|
2020-07-14 16:47:46 +02:00
|
|
|
merge_method = module.params['merge_method']
|
2020-03-09 10:11:07 +01:00
|
|
|
wiki_enabled = module.params['wiki_enabled']
|
|
|
|
snippets_enabled = module.params['snippets_enabled']
|
|
|
|
visibility = module.params['visibility']
|
|
|
|
import_url = module.params['import_url']
|
|
|
|
state = module.params['state']
|
2020-12-27 10:55:06 +01:00
|
|
|
lfs_enabled = module.params['lfs_enabled']
|
2021-06-27 16:09:41 +02:00
|
|
|
username = module.params['username']
|
2021-07-19 11:52:32 +02:00
|
|
|
allow_merge_on_skipped_pipeline = module.params['allow_merge_on_skipped_pipeline']
|
|
|
|
only_allow_merge_if_all_discussions_are_resolved = module.params['only_allow_merge_if_all_discussions_are_resolved']
|
|
|
|
only_allow_merge_if_pipeline_succeeds = module.params['only_allow_merge_if_pipeline_succeeds']
|
|
|
|
packages_enabled = module.params['packages_enabled']
|
|
|
|
remove_source_branch_after_merge = module.params['remove_source_branch_after_merge']
|
|
|
|
squash_option = module.params['squash_option']
|
2021-09-20 06:54:43 +02:00
|
|
|
ci_config_path = module.params['ci_config_path']
|
|
|
|
shared_runners_enabled = module.params['shared_runners_enabled']
|
2021-11-30 06:12:28 +01:00
|
|
|
avatar_path = module.params['avatar_path']
|
|
|
|
default_branch = module.params['default_branch']
|
2024-08-11 20:21:49 +02:00
|
|
|
repository_access_level = module.params['repository_access_level']
|
2023-01-12 21:06:52 +01:00
|
|
|
builds_access_level = module.params['builds_access_level']
|
|
|
|
forking_access_level = module.params['forking_access_level']
|
|
|
|
container_registry_access_level = module.params['container_registry_access_level']
|
2023-02-26 14:13:22 +01:00
|
|
|
releases_access_level = module.params['releases_access_level']
|
|
|
|
environments_access_level = module.params['environments_access_level']
|
|
|
|
feature_flags_access_level = module.params['feature_flags_access_level']
|
|
|
|
infrastructure_access_level = module.params['infrastructure_access_level']
|
|
|
|
monitor_access_level = module.params['monitor_access_level']
|
|
|
|
security_and_compliance_access_level = module.params['security_and_compliance_access_level']
|
2023-04-03 22:02:20 +02:00
|
|
|
topics = module.params['topics']
|
2024-08-11 20:21:49 +02:00
|
|
|
container_expiration_policy = module.params['container_expiration_policy']
|
2021-11-30 06:12:28 +01:00
|
|
|
|
2020-03-09 10:11:07 +01:00
|
|
|
# Set project_path to project_name if it is empty.
|
|
|
|
if project_path is None:
|
|
|
|
project_path = project_name.replace(" ", "_")
|
|
|
|
|
|
|
|
gitlab_project = GitLabProject(module, gitlab_instance)
|
|
|
|
|
2020-12-27 10:55:06 +01:00
|
|
|
namespace = None
|
2021-06-27 14:01:06 +02:00
|
|
|
namespace_id = None
|
2020-03-09 10:11:07 +01:00
|
|
|
if group_identifier:
|
2021-11-16 13:01:32 +01:00
|
|
|
group = find_group(gitlab_instance, group_identifier)
|
2020-03-09 10:11:07 +01:00
|
|
|
if group is None:
|
|
|
|
module.fail_json(msg="Failed to create project: group %s doesn't exists" % group_identifier)
|
|
|
|
|
2021-06-27 14:01:06 +02:00
|
|
|
namespace_id = group.id
|
2020-03-09 10:11:07 +01:00
|
|
|
else:
|
2021-06-27 16:09:41 +02:00
|
|
|
if username:
|
2022-04-13 13:32:25 +02:00
|
|
|
namespace = gitlab_instance.namespaces.list(search=username, all=False)[0]
|
2021-06-27 16:09:41 +02:00
|
|
|
else:
|
2022-04-13 13:32:25 +02:00
|
|
|
namespace = gitlab_instance.namespaces.list(search=gitlab_instance.user.username, all=False)[0]
|
2021-06-27 14:01:06 +02:00
|
|
|
namespace_id = namespace.id
|
2020-12-27 10:55:06 +01:00
|
|
|
|
2021-06-27 14:01:06 +02:00
|
|
|
if not namespace_id:
|
|
|
|
module.fail_json(msg="Failed to find the namespace or group ID which is required to look up the namespace")
|
2020-12-27 10:55:06 +01:00
|
|
|
|
|
|
|
try:
|
2021-06-27 14:01:06 +02:00
|
|
|
namespace = gitlab_instance.namespaces.get(namespace_id)
|
2020-12-27 10:55:06 +01:00
|
|
|
except gitlab.exceptions.GitlabGetError as e:
|
|
|
|
module.fail_json(msg="Failed to find the namespace for the given user: %s" % to_native(e))
|
|
|
|
|
|
|
|
if not namespace:
|
|
|
|
module.fail_json(msg="Failed to find the namespace for the project")
|
2021-11-16 13:01:32 +01:00
|
|
|
project_exists = gitlab_project.exists_project(namespace, project_path)
|
2020-03-09 10:11:07 +01:00
|
|
|
|
|
|
|
if state == 'absent':
|
|
|
|
if project_exists:
|
2021-11-16 13:01:32 +01:00
|
|
|
gitlab_project.delete_project()
|
2020-03-09 10:11:07 +01:00
|
|
|
module.exit_json(changed=True, msg="Successfully deleted project %s" % project_name)
|
2020-12-27 10:55:06 +01:00
|
|
|
module.exit_json(changed=False, msg="Project deleted or does not exists")
|
2020-03-09 10:11:07 +01:00
|
|
|
|
|
|
|
if state == 'present':
|
2021-07-19 11:52:32 +02:00
|
|
|
|
2023-09-06 19:11:11 +02:00
|
|
|
if gitlab_project.create_or_update_project(module, project_name, namespace, {
|
2021-11-16 13:01:32 +01:00
|
|
|
"path": project_path,
|
|
|
|
"description": project_description,
|
|
|
|
"initialize_with_readme": initialize_with_readme,
|
2021-11-30 06:12:28 +01:00
|
|
|
"default_branch": default_branch,
|
2021-11-16 13:01:32 +01:00
|
|
|
"issues_enabled": issues_enabled,
|
|
|
|
"merge_requests_enabled": merge_requests_enabled,
|
|
|
|
"merge_method": merge_method,
|
|
|
|
"wiki_enabled": wiki_enabled,
|
|
|
|
"snippets_enabled": snippets_enabled,
|
|
|
|
"visibility": visibility,
|
|
|
|
"import_url": import_url,
|
|
|
|
"lfs_enabled": lfs_enabled,
|
|
|
|
"allow_merge_on_skipped_pipeline": allow_merge_on_skipped_pipeline,
|
|
|
|
"only_allow_merge_if_all_discussions_are_resolved": only_allow_merge_if_all_discussions_are_resolved,
|
|
|
|
"only_allow_merge_if_pipeline_succeeds": only_allow_merge_if_pipeline_succeeds,
|
|
|
|
"packages_enabled": packages_enabled,
|
|
|
|
"remove_source_branch_after_merge": remove_source_branch_after_merge,
|
|
|
|
"squash_option": squash_option,
|
|
|
|
"ci_config_path": ci_config_path,
|
|
|
|
"shared_runners_enabled": shared_runners_enabled,
|
2021-11-30 06:12:28 +01:00
|
|
|
"avatar_path": avatar_path,
|
2024-08-11 20:21:49 +02:00
|
|
|
"repository_access_level": repository_access_level,
|
2023-01-12 21:06:52 +01:00
|
|
|
"builds_access_level": builds_access_level,
|
|
|
|
"forking_access_level": forking_access_level,
|
|
|
|
"container_registry_access_level": container_registry_access_level,
|
2023-02-26 14:13:22 +01:00
|
|
|
"releases_access_level": releases_access_level,
|
|
|
|
"environments_access_level": environments_access_level,
|
|
|
|
"feature_flags_access_level": feature_flags_access_level,
|
|
|
|
"infrastructure_access_level": infrastructure_access_level,
|
|
|
|
"monitor_access_level": monitor_access_level,
|
|
|
|
"security_and_compliance_access_level": security_and_compliance_access_level,
|
2023-04-03 22:02:20 +02:00
|
|
|
"topics": topics,
|
2024-08-11 20:21:49 +02:00
|
|
|
"container_expiration_policy": container_expiration_policy,
|
2021-11-16 13:01:32 +01:00
|
|
|
}):
|
|
|
|
|
|
|
|
module.exit_json(changed=True, msg="Successfully created or updated the project %s" % project_name, project=gitlab_project.project_object._attrs)
|
|
|
|
module.exit_json(changed=False, msg="No need to update the project %s" % project_name, project=gitlab_project.project_object._attrs)
|
2020-03-09 10:11:07 +01:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|