diff --git a/Makefile b/Makefile index 8a5c11f..6e4f53e 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,8 @@ GOFMT_FILES?=$$(find . -name '*.go' |grep -v vendor) GOFMT ?= gofmt -s +VERSION = 0.6.1 + test: fmt-check go test -i $(TEST) || exit 1 echo $(TEST) | \ @@ -28,3 +30,12 @@ fmt-check: echo "$${diff}"; \ exit 1; \ fi; +build: + go build -o terraform-provider-gitea_${VERSION} +install: build + @echo installing to + @echo ~/.terraform.d/plugins/terraform.local/local/gitea/${VERSION}/linux_amd64/terraform-provider-gitea_${VERSION} + @mkdir -p ~/.terraform.d/plugins/terraform.local/local/gitea/${VERSION}/linux_amd64 + @mv terraform-provider-gitea_${VERSION} ~/.terraform.d/plugins/terraform.local/local/gitea/${VERSION}/linux_amd64/terraform-provider-gitea_${VERSION} +doc: + tfplugindocs \ No newline at end of file diff --git a/docs/data-sources/org.md b/docs/data-sources/org.md new file mode 100644 index 0000000..2741619 --- /dev/null +++ b/docs/data-sources/org.md @@ -0,0 +1,32 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "gitea_org Data Source - terraform-provider-gitea" +subcategory: "" +description: |- + +--- + +# gitea_org (Data Source) + + + + + + +## Schema + +### Optional + +- `name` (String) + +### Read-Only + +- `avatar_url` (String) +- `description` (String) +- `full_name` (String) +- `id` (Number) The ID of this resource. +- `location` (String) +- `visibility` (String) +- `website` (String) + + diff --git a/docs/data-sources/repo.md b/docs/data-sources/repo.md new file mode 100644 index 0000000..0bbe6c6 --- /dev/null +++ b/docs/data-sources/repo.md @@ -0,0 +1,47 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "gitea_repo Data Source - terraform-provider-gitea" +subcategory: "" +description: |- + +--- + +# gitea_repo (Data Source) + + + + + + +## Schema + +### Required + +- `name` (String) +- `username` (String) + +### Read-Only + +- `clone_url` (String) +- `created` (String) +- `default_branch` (String) +- `description` (String) +- `fork` (Boolean) +- `forks` (Number) +- `full_name` (String) +- `html_url` (String) +- `id` (String) The ID of this resource. +- `mirror` (Boolean) +- `open_issue_count` (Number) +- `permission_admin` (Boolean) +- `permission_pull` (Boolean) +- `permission_push` (Boolean) +- `private` (Boolean) +- `size` (Number) +- `ssh_url` (String) +- `stars` (Number) +- `updated` (String) +- `watchers` (Number) +- `website` (String) + + diff --git a/docs/data-sources/user.md b/docs/data-sources/user.md new file mode 100644 index 0000000..b86c466 --- /dev/null +++ b/docs/data-sources/user.md @@ -0,0 +1,33 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "gitea_user Data Source - terraform-provider-gitea" +subcategory: "" +description: |- + +--- + +# gitea_user (Data Source) + + + + + + +## Schema + +### Optional + +- `username` (String) + +### Read-Only + +- `avatar_url` (String) +- `created` (String) +- `email` (String) +- `full_name` (String) +- `id` (Number) The ID of this resource. +- `is_admin` (Boolean) +- `language` (String) +- `last_login` (String) + + diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..9d0f6e2 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,52 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "gitea Provider" +subcategory: "" +description: |- + +--- + +# gitea Provider + + + +## Example Usage + +```terraform +terraform { + required_providers { + gitea = { + source = "gitea/gitea" + version = "0.6.1" + } + } +} + +provider "gitea" { + base_url = var.gitea_url # optionally use GITEA_BASE_URL env var + token = var.gitea_token # optionally use GITEA_TOKEN env var + + # Username/Password authentication is mutally exclusive with token authentication + # username = var.username # optionally use GITEA_USERNAME env var + # password = var.password # optionally use GITEA_PASSWORD env var + + # A file containing the ca certificate to use in case ssl certificate is not from a standard chain + cacert_file = var.cacert_file + + # If you are running a gitea instance with self signed TLS certificates + # and you want to disable certificate validation you can deactivate it with this flag + insecure = false +} +``` + + +## Schema + +### Optional + +- `base_url` (String) The Gitea Base API URL +- `cacert_file` (String) A file containing the ca certificate to use in case ssl certificate is not from a standard chain +- `insecure` (Boolean) Disable SSL verification of API calls +- `password` (String) Password in case of using basic auth +- `token` (String) The application token used to connect to Gitea. +- `username` (String) Username in case of using basic auth diff --git a/docs/resources/oauth2_app.md b/docs/resources/oauth2_app.md new file mode 100644 index 0000000..9dd1740 --- /dev/null +++ b/docs/resources/oauth2_app.md @@ -0,0 +1,29 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "gitea_oauth2_app Resource - terraform-provider-gitea" +subcategory: "" +description: |- + Handling gitea oauth application https://docs.gitea.io/en-us/oauth2-provider/ resources +--- + +# gitea_oauth2_app (Resource) + +Handling [gitea oauth application](https://docs.gitea.io/en-us/oauth2-provider/) resources + + + + +## Schema + +### Required + +- `name` (String) OAuth Application name +- `redirect_uris` (Set of String) Accepted redirect URIs + +### Read-Only + +- `client_id` (String) OAuth2 Application client id +- `client_secret` (String, Sensitive) Oauth2 Application client secret +- `id` (String) The ID of this resource. + + diff --git a/docs/resources/org.md b/docs/resources/org.md new file mode 100644 index 0000000..d09f7c0 --- /dev/null +++ b/docs/resources/org.md @@ -0,0 +1,50 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "gitea_org Resource - terraform-provider-gitea" +subcategory: "" +description: |- + gitea_org manages a gitea organisation. + Organisations are a way to group repositories and abstract permission management in a gitea instance. +--- + +# gitea_org (Resource) + +`gitea_org` manages a gitea organisation. + +Organisations are a way to group repositories and abstract permission management in a gitea instance. + +## Example Usage + +```terraform +resource "gitea_org" "test_org" { + name = "test-org" +} + +resource "gitea_repository" "org_repo" { + username = gitea_org.test_org.name + name = "org-test-repo" +} +``` + + +## Schema + +### Required + +- `name` (String) The name of the organisation without spaces. + +### Optional + +- `description` (String) A description of this organisation. +- `full_name` (String) The display name of the organisation. Defaults to the value of `name`. +- `location` (String) +- `repo_admin_change_team_access` (Boolean) +- `visibility` (String) Flag is this organisation should be publicly visible or not. +- `website` (String) A link to a website with more information about this organisation. + +### Read-Only + +- `avatar_url` (String) +- `id` (String) The ID of this resource. + + diff --git a/docs/resources/public_key.md b/docs/resources/public_key.md new file mode 100644 index 0000000..aba61fc --- /dev/null +++ b/docs/resources/public_key.md @@ -0,0 +1,52 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "gitea_public_key Resource - terraform-provider-gitea" +subcategory: "" +description: |- + gitea_public_key manages ssh key that are associated with users. +--- + +# gitea_public_key (Resource) + +`gitea_public_key` manages ssh key that are associated with users. + +## Example Usage + +```terraform +resource "gitea_user" "test" { + username = "test" + login_name = "test" + password = "Geheim1!" + email = "test@user.dev" + must_change_password = false +} + + +resource "gitea_public_key" "test_user_key" { + title = "test" + key = file("${path.module}/id_ed25519.pub") + username = gitea_user.test.username +} +``` + + +## Schema + +### Required + +- `key` (String, Sensitive) An armored SSH key to add +- `title` (String) Title of the key to add +- `username` (String) User to associate with the added key + +### Optional + +- `read_only` (Boolean) Describe if the key has only read access or read/write + +### Read-Only + +- `created` (String) +- `fingerprint` (String) +- `id` (String) The ID of this resource. +- `type` (String) + + diff --git a/docs/resources/repository.md b/docs/resources/repository.md new file mode 100644 index 0000000..600ba78 --- /dev/null +++ b/docs/resources/repository.md @@ -0,0 +1,109 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "gitea_repository Resource - terraform-provider-gitea" +subcategory: "" +description: |- + gitea_repository manages a gitea repository. + Per default this repository will be initializiled with the provided configuration (gitignore, License etc.). + If the username property is set to a organisation name, the provider will try to look if this organisation exists and create the repository under the organisation scope. + Repository migrations have some properties that are not available to regular repositories. These are all prefixed with migration_. +--- + +# gitea_repository (Resource) + +`gitea_repository` manages a gitea repository. + +Per default this repository will be initializiled with the provided configuration (gitignore, License etc.). +If the `username` property is set to a organisation name, the provider will try to look if this organisation exists and create the repository under the organisation scope. + +Repository migrations have some properties that are not available to regular repositories. These are all prefixed with `migration_`. + +## Example Usage + +```terraform +resource "gitea_user" "test" { + username = "test" + login_name = "test" + password = "Geheim1!" + email = "test@user.dev" + must_change_password = false +} + +resource "gitea_repository" "test" { + username = resource.gitea_user.test.name + name = "test" + private = true + issue_labels = "Default" + license = "MIT" + gitignores = "Go" +} + +resource "gitea_repository" "mirror" { + username = resource.gitea_user.test.name + name = "terraform-provider-gitea-mirror" + description = "Mirror of Terraform Provider" + mirror = true + migration_clone_addresse = "https://git.uploadfilter24.eu/lerentis/terraform-provider-gitea.git" + migration_service = "gitea" + migration_service_auth_token = var.gitea_mirror_token +} +``` + + +## Schema + +### Required + +- `name` (String) The Name of the repository +- `username` (String) The Owner of the repository + +### Optional + +- `allow_manual_merge` (Boolean) +- `allow_merge_commits` (Boolean) +- `allow_rebase` (Boolean) +- `allow_rebase_explicit` (Boolean) +- `allow_squash_merge` (Boolean) +- `archived` (Boolean) +- `auto_init` (Boolean) Flag if the repository should be initiated with the configured values +- `autodetect_manual_merge` (Boolean) +- `default_branch` (String) The default branch of the repository. Defaults to `main` +- `description` (String) The description of the repository. +- `gitignores` (String) A specific gitignore that should be commited to the repositoryon creation if `auto_init` is set to `true` +Need to exist in the gitea instance +- `has_issues` (Boolean) A flag if the repository should have issue management enabled or not. +- `has_projects` (Boolean) A flag if the repository should have the native project management enabled or not. +- `has_pull_requests` (Boolean) A flag if the repository should acceppt pull requests or not. +- `has_wiki` (Boolean) A flag if the repository should have the native wiki enabled or not. +- `ignore_whitespace_conflicts` (Boolean) +- `issue_labels` (String) The Issue Label configuration to be used in this repository. +Need to exist in the gitea instance +- `license` (String) The license under which the source code of this repository should be. +Need to exist in the gitea instance +- `migration_clone_addresse` (String) +- `migration_issue_labels` (Boolean) +- `migration_lfs` (Boolean) +- `migration_lfs_endpoint` (String) +- `migration_milestones` (Boolean) +- `migration_mirror_interval` (String) valid time units are 'h', 'm', 's'. 0 to disable automatic sync +- `migration_releases` (Boolean) +- `migration_service` (String) git/github/gitlab/gitea/gogs +- `migration_service_auth_password` (String, Sensitive) +- `migration_service_auth_token` (String, Sensitive) +- `migration_service_auth_username` (String) +- `mirror` (Boolean) +- `private` (Boolean) Flag if the repository should be private or not. +- `readme` (String) +- `repo_template` (Boolean) +- `website` (String) A link to a website with more information. + +### Read-Only + +- `created` (String) +- `id` (String) The ID of this resource. +- `permission_admin` (Boolean) +- `permission_pull` (Boolean) +- `permission_push` (Boolean) +- `updated` (String) + + diff --git a/docs/resources/team.md b/docs/resources/team.md new file mode 100644 index 0000000..6434ca7 --- /dev/null +++ b/docs/resources/team.md @@ -0,0 +1,62 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "gitea_team Resource - terraform-provider-gitea" +subcategory: "" +description: |- + gitea_team manages Team that are part of an organisation. +--- + +# gitea_team (Resource) + +`gitea_team` manages Team that are part of an organisation. + +## Example Usage + +```terraform +resource "gitea_org" "test_org" { + name = "test-org" +} + +resource "gitea_user" "test" { + username = "test" + login_name = "test" + password = "Geheim1!" + email = "test@user.dev" + must_change_password = false + admin = true +} + + +resource "gitea_team" "test_team" { + name = "Devs" + organisation = gitea_org.test_org.name + description = "Devs of Test Org" + permission = "write" + members = [gitea_user.test.username] +} +``` + + +## Schema + +### Required + +- `name` (String) Name of the Team +- `organisation` (String) The organisation which this Team is part of. + +### Optional + +- `can_create_repos` (Boolean) Flag if the Teams members should be able to create Rpositories in the Organisation +- `description` (String) Description of the Team +- `include_all_repositories` (Boolean) Flag if the Teams members should have access to all Repositories in the Organisation +- `members` (List of String) List of Users that should be part of this team +- `permission` (String) Permissions associated with this Team +Can be `none`, `read`, `write`, `admin` or `owner` +- `units` (String) List of types of Repositories that should be allowed to be created from Team members. +Can be `repo.code`, `repo.issues`, `repo.ext_issues`, `repo.wiki`, `repo.pulls`, `repo.releases`, `repo.projects` and/or `repo.ext_wiki` + +### Read-Only + +- `id` (String) The ID of this resource. + + diff --git a/docs/resources/user.md b/docs/resources/user.md new file mode 100644 index 0000000..a992c95 --- /dev/null +++ b/docs/resources/user.md @@ -0,0 +1,60 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "gitea_user Resource - terraform-provider-gitea" +subcategory: "" +description: |- + gitea_user manages a native gitea user. + If you are using OIDC or other kinds of authentication mechanisms you can still try to managessh keys or other ressources this way +--- + +# gitea_user (Resource) + +`gitea_user` manages a native gitea user. + +If you are using OIDC or other kinds of authentication mechanisms you can still try to managessh keys or other ressources this way + +## Example Usage + +```terraform +resource "gitea_user" "test" { + username = "test" + login_name = "test" + password = "Geheim1!" + email = "test@user.dev" + must_change_password = false +} +``` + + +## Schema + +### Required + +- `email` (String) E-Mail Address of the user +- `login_name` (String) The login name can differ from the username +- `password` (String, Sensitive) Password to be set for the user +- `username` (String) Username of the user to be created + +### Optional + +- `active` (Boolean) Flag if this user should be active or not +- `admin` (Boolean) Flag if this user should be an administrator or not +- `allow_create_organization` (Boolean) +- `allow_git_hook` (Boolean) +- `allow_import_local` (Boolean) +- `description` (String) A description of the user +- `force_password_change` (Boolean) Flag if the user defined password should be overwritten or not +- `full_name` (String) Full name of the user +- `location` (String) +- `max_repo_creation` (Number) +- `must_change_password` (Boolean) Flag if the user should change the password after first login +- `prohibit_login` (Boolean) Flag if the user should not be allowed to log in (bot user) +- `restricted` (Boolean) +- `send_notification` (Boolean) Flag to send a notification about the user creation to the defined `email` +- `visibility` (String) Visibility of the user. Can be `public`, `limited` or `private` + +### Read-Only + +- `id` (String) The ID of this resource. + + diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 0000000..85b87b4 --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1,6 @@ +.terraform +.terraform.lock.hcl +terraform.tfstate +terraform.tfstate.backup +*.tfvars +id_ed25519 \ No newline at end of file diff --git a/examples/provider/provider.tf b/examples/provider/provider.tf new file mode 100644 index 0000000..655382c --- /dev/null +++ b/examples/provider/provider.tf @@ -0,0 +1,24 @@ +terraform { + required_providers { + gitea = { + source = "gitea/gitea" + version = "0.6.1" + } + } +} + +provider "gitea" { + base_url = var.gitea_url # optionally use GITEA_BASE_URL env var + token = var.gitea_token # optionally use GITEA_TOKEN env var + + # Username/Password authentication is mutally exclusive with token authentication + # username = var.username # optionally use GITEA_USERNAME env var + # password = var.password # optionally use GITEA_PASSWORD env var + + # A file containing the ca certificate to use in case ssl certificate is not from a standard chain + cacert_file = var.cacert_file + + # If you are running a gitea instance with self signed TLS certificates + # and you want to disable certificate validation you can deactivate it with this flag + insecure = false +} \ No newline at end of file diff --git a/examples/resources/gitea_org/resource.tf b/examples/resources/gitea_org/resource.tf new file mode 100644 index 0000000..c864c01 --- /dev/null +++ b/examples/resources/gitea_org/resource.tf @@ -0,0 +1,8 @@ +resource "gitea_org" "test_org" { + name = "test-org" +} + +resource "gitea_repository" "org_repo" { + username = gitea_org.test_org.name + name = "org-test-repo" +} \ No newline at end of file diff --git a/examples/resources/gitea_public_key/id_ed25519.pub b/examples/resources/gitea_public_key/id_ed25519.pub new file mode 100644 index 0000000..a6f4571 --- /dev/null +++ b/examples/resources/gitea_public_key/id_ed25519.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINn6hAP48oKz6MVWjYvn0fne2YeaOv/zC6zuvFXlJKf2 test@dev.local diff --git a/examples/resources/gitea_public_key/resource.tf b/examples/resources/gitea_public_key/resource.tf new file mode 100644 index 0000000..1862438 --- /dev/null +++ b/examples/resources/gitea_public_key/resource.tf @@ -0,0 +1,14 @@ +resource "gitea_user" "test" { + username = "test" + login_name = "test" + password = "Geheim1!" + email = "test@user.dev" + must_change_password = false +} + + +resource "gitea_public_key" "test_user_key" { + title = "test" + key = file("${path.module}/id_ed25519.pub") + username = gitea_user.test.username +} diff --git a/examples/resources/gitea_repository/resource.tf b/examples/resources/gitea_repository/resource.tf new file mode 100644 index 0000000..db9a55d --- /dev/null +++ b/examples/resources/gitea_repository/resource.tf @@ -0,0 +1,26 @@ +resource "gitea_user" "test" { + username = "test" + login_name = "test" + password = "Geheim1!" + email = "test@user.dev" + must_change_password = false +} + +resource "gitea_repository" "test" { + username = resource.gitea_user.test.name + name = "test" + private = true + issue_labels = "Default" + license = "MIT" + gitignores = "Go" +} + +resource "gitea_repository" "mirror" { + username = resource.gitea_user.test.name + name = "terraform-provider-gitea-mirror" + description = "Mirror of Terraform Provider" + mirror = true + migration_clone_addresse = "https://git.uploadfilter24.eu/lerentis/terraform-provider-gitea.git" + migration_service = "gitea" + migration_service_auth_token = var.gitea_mirror_token +} diff --git a/examples/resources/gitea_team/resource.tf b/examples/resources/gitea_team/resource.tf new file mode 100644 index 0000000..7accce0 --- /dev/null +++ b/examples/resources/gitea_team/resource.tf @@ -0,0 +1,21 @@ +resource "gitea_org" "test_org" { + name = "test-org" +} + +resource "gitea_user" "test" { + username = "test" + login_name = "test" + password = "Geheim1!" + email = "test@user.dev" + must_change_password = false + admin = true +} + + +resource "gitea_team" "test_team" { + name = "Devs" + organisation = gitea_org.test_org.name + description = "Devs of Test Org" + permission = "write" + members = [gitea_user.test.username] +} diff --git a/examples/resources/gitea_user/resource.tf b/examples/resources/gitea_user/resource.tf new file mode 100644 index 0000000..1b97b0f --- /dev/null +++ b/examples/resources/gitea_user/resource.tf @@ -0,0 +1,7 @@ +resource "gitea_user" "test" { + username = "test" + login_name = "test" + password = "Geheim1!" + email = "test@user.dev" + must_change_password = false +} \ No newline at end of file diff --git a/gitea/config.go b/gitea/config.go index 2003f52..39bd524 100644 --- a/gitea/config.go +++ b/gitea/config.go @@ -61,12 +61,11 @@ func (c *Config) Client() (interface{}, error) { var client *gitea.Client if c.Token != "" { - client, _ = gitea.NewClient(c.BaseURL, gitea.SetToken(c.Token)) + client, _ = gitea.NewClient(c.BaseURL, gitea.SetToken(c.Token), gitea.SetHTTPClient(httpClient)) } - client.SetHTTPClient(httpClient) if c.Username != "" { - client.SetBasicAuth(c.Username, c.Password) + client, _ = gitea.NewClient(c.BaseURL, gitea.SetBasicAuth(c.Username, c.Password), gitea.SetHTTPClient(httpClient)) } // Test the credentials by checking we can get information about the authenticated user. diff --git a/gitea/provider.go b/gitea/provider.go index 9909fae..11daf15 100644 --- a/gitea/provider.go +++ b/gitea/provider.go @@ -73,10 +73,14 @@ func Provider() terraform.ResourceProvider { }, ResourcesMap: map[string]*schema.Resource{ - // "gitea_org": resourceGiteaOrg(), + "gitea_org": resourceGiteaOrg(), // "gitea_team": resourceGiteaTeam(), // "gitea_repo": resourceGiteaRepo(), - // "gitea_user": resourceGiteaUser(), + "gitea_user": resourceGiteaUser(), + "gitea_oauth2_app": resourceGiteaOauthApp(), + "gitea_repository": resourceGiteaRepository(), + "gitea_public_key": resourceGiteaPublicKey(), + "gitea_team": resourceGiteaTeam(), }, ConfigureFunc: providerConfigure, diff --git a/gitea/resource_gitea_oauth_app.go b/gitea/resource_gitea_oauth_app.go new file mode 100644 index 0000000..4ff217f --- /dev/null +++ b/gitea/resource_gitea_oauth_app.go @@ -0,0 +1,195 @@ +package gitea + +import ( + "fmt" + + "code.gitea.io/sdk/gitea" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +const ( + oauth2KeyName string = "name" + oauth2KeyRedirectURIs string = "redirect_uris" + oauth2KeyClientId string = "client_id" + oauth2KeyClientSecret string = "client_secret" +) + +func resourceGiteaOauthApp() *schema.Resource { + return &schema.Resource{ + Read: resourceOauth2AppRead, + Create: resourceOauth2AppUpcreate, + Update: resourceOauth2AppUpcreate, + Delete: resourceOauth2AppDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Schema: map[string]*schema.Schema{ + oauth2KeyName: { + Required: true, + Type: schema.TypeString, + Description: "OAuth Application name", + }, + oauth2KeyRedirectURIs: { + Required: true, + Type: schema.TypeSet, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Description: "Accepted redirect URIs", + }, + oauth2KeyClientId: { + Type: schema.TypeString, + Computed: true, + Description: "OAuth2 Application client id", + }, + oauth2KeyClientSecret: { + Type: schema.TypeString, + Computed: true, + Sensitive: true, + Description: "Oauth2 Application client secret", + }, + }, + Description: "Handling [gitea oauth application](https://docs.gitea.io/en-us/oauth2-provider/) resources", + } +} + +func ExpandStringList(configured []interface{}) []string { + res := make([]string, 0, len(configured)) + for _, v := range configured { + val, ok := v.(string) + if ok && val != "" { + res = append(res, (v.(string))) + } + } + return res +} + +func CollapseStringList(strlist []string) []interface{} { + res := make([]interface{}, 0, len(strlist)) + for _, v := range strlist { + res = append(res, v) + } + return res +} + +func resourceOauth2AppUpcreate(d *schema.ResourceData, meta interface{}) (err error) { + client := meta.(*gitea.Client) + + redirectURIsSchema, redirectURIsSchemaOk := d.Get(oauth2KeyRedirectURIs).(*schema.Set) + + if !redirectURIsSchemaOk { + return fmt.Errorf("attribute %s must be set to a set of strings", oauth2KeyRedirectURIs) + } + + redirectURIs := ExpandStringList(redirectURIsSchema.List()) + + name, nameOk := d.Get(oauth2KeyName).(string) + + if !nameOk { + return fmt.Errorf("attribute %s must be set and must be a string", oauth2KeyName) + } + + opts := gitea.CreateOauth2Option{ + Name: name, + RedirectURIs: redirectURIs, + } + + var oauth2 *gitea.Oauth2 + + if d.IsNewResource() { + oauth2, _, err = client.CreateOauth2(opts) + } else { + oauth2, err := searchOauth2AppByClientId(client, d.Id()) + + if err != nil { + return err + } + + oauth2, _, err = client.UpdateOauth2(oauth2.ID, opts) + } + + if err != nil { + return + } + + err = setOAuth2ResourceData(oauth2, d) + + return +} + +func searchOauth2AppByClientId(c *gitea.Client, id string) (res *gitea.Oauth2, err error) { + page := 1 + + for { + apps, _, err := c.ListOauth2(gitea.ListOauth2Option{ + ListOptions: gitea.ListOptions{ + Page: page, + PageSize: 50, + }, + }) + if err != nil { + return nil, err + } + if len(apps) == 0 { + return nil, fmt.Errorf("no oauth client can be found by id '%s'", id) + } + + for _, app := range apps { + if app.ClientID == id { + return app, nil + } + } + + page += 1 + } +} + +func resourceOauth2AppRead(d *schema.ResourceData, meta interface{}) (err error) { + client := meta.(*gitea.Client) + + app, err := searchOauth2AppByClientId(client, d.Id()) + + if err != nil { + return err + } + + err = setOAuth2ResourceData(app, d) + + return +} + +func resourceOauth2AppDelete(d *schema.ResourceData, meta interface{}) (err error) { + client := meta.(*gitea.Client) + + app, err := searchOauth2AppByClientId(client, d.Id()) + + if err != nil { + return err + } + + _, err = client.DeleteOauth2(app.ID) + + return +} + +func setOAuth2ResourceData(app *gitea.Oauth2, d *schema.ResourceData) (err error) { + d.SetId(app.ClientID) + + for k, v := range map[string]interface{}{ + oauth2KeyName: app.Name, + oauth2KeyRedirectURIs: schema.NewSet(schema.HashString, CollapseStringList(app.RedirectURIs)), + oauth2KeyClientId: app.ClientID, + } { + err = d.Set(k, v) + if err != nil { + return + } + } + + if app.ClientSecret != "" { + // Gitea API only reports client secrets if the resource is newly created + d.Set(oauth2KeyClientSecret, app.ClientSecret) + } + + return +} diff --git a/gitea/resource_gitea_organisation.go b/gitea/resource_gitea_organisation.go new file mode 100644 index 0000000..be78e41 --- /dev/null +++ b/gitea/resource_gitea_organisation.go @@ -0,0 +1,190 @@ +package gitea + +import ( + "fmt" + + "code.gitea.io/sdk/gitea" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +const ( + orgName string = "name" + orgFullName string = "full_name" + orgDescription string = "description" + orgWebsite string = "website" + orgLocation string = "location" + orgVisibility string = "visibility" + RepoAdminChangeTeamAccess string = "repo_admin_change_team_access" +) + +func resourceOrgRead(d *schema.ResourceData, meta interface{}) (err error) { + client := meta.(*gitea.Client) + + var org *gitea.Organization + var resp *gitea.Response + + org, resp, err = client.GetOrg(d.Get(orgName).(string)) + + if err != nil { + if resp.StatusCode == 404 { + d.SetId("") + return nil + } else { + return err + } + } + + err = setOrgResourceData(org, d) + + return +} + +func resourceOrgCreate(d *schema.ResourceData, meta interface{}) (err error) { + client := meta.(*gitea.Client) + + opts := gitea.CreateOrgOption{ + Name: d.Get(orgName).(string), + FullName: d.Get(orgFullName).(string), + Description: d.Get(orgDescription).(string), + Website: d.Get(orgWebsite).(string), + Location: d.Get(orgLocation).(string), + Visibility: gitea.VisibleType(d.Get(orgVisibility).(string)), + RepoAdminChangeTeamAccess: d.Get(RepoAdminChangeTeamAccess).(bool), + } + + org, _, err := client.CreateOrg(opts) + if err != nil { + return + } + + err = setOrgResourceData(org, d) + + return +} + +func resourceOrgUpdate(d *schema.ResourceData, meta interface{}) (err error) { + client := meta.(*gitea.Client) + + var org *gitea.Organization + var resp *gitea.Response + + org, resp, err = client.GetOrg(d.Get(orgName).(string)) + + if err != nil { + if resp.StatusCode == 404 { + resourceOrgCreate(d, meta) + } else { + return err + } + } + + opts := gitea.EditOrgOption{ + FullName: d.Get(orgFullName).(string), + Description: d.Get(orgDescription).(string), + Website: d.Get(orgWebsite).(string), + Location: d.Get(orgLocation).(string), + Visibility: gitea.VisibleType(d.Get(orgVisibility).(string)), + } + + client.EditOrg(d.Get(orgName).(string), opts) + + org, resp, err = client.GetOrg(d.Get(orgName).(string)) + + err = setOrgResourceData(org, d) + + return +} + +func resourceOrgDelete(d *schema.ResourceData, meta interface{}) (err error) { + client := meta.(*gitea.Client) + + var resp *gitea.Response + + resp, err = client.DeleteOrg(d.Get(orgName).(string)) + + if err != nil { + if resp.StatusCode == 404 { + return + } else { + return err + } + } + + return +} + +func setOrgResourceData(org *gitea.Organization, d *schema.ResourceData) (err error) { + d.SetId(fmt.Sprintf("%d", org.ID)) + d.Set("name", org.UserName) + d.Set("full_name", org.FullName) + d.Set("avatar_url", org.AvatarURL) + d.Set("description", org.Description) + d.Set("website", org.Website) + d.Set("location", org.Location) + d.Set("visibility", org.Visibility) + + return +} + +func resourceGiteaOrg() *schema.Resource { + return &schema.Resource{ + Read: resourceOrgRead, + Create: resourceOrgCreate, + Update: resourceOrgUpdate, + Delete: resourceOrgDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The name of the organisation without spaces.", + }, + "full_name": { + Type: schema.TypeString, + Required: false, + Optional: true, + Description: "The display name of the organisation. Defaults to the value of `name`.", + }, + "description": { + Type: schema.TypeString, + Required: false, + Optional: true, + Description: "A description of this organisation.", + }, + "website": { + Type: schema.TypeString, + Required: false, + Optional: true, + Description: "A link to a website with more information about this organisation.", + }, + "location": { + Type: schema.TypeString, + Required: false, + Optional: true, + }, + "repo_admin_change_team_access": { + Type: schema.TypeBool, + Required: false, + Optional: true, + Default: true, + }, + "avatar_url": { + Type: schema.TypeString, + Required: false, + Computed: true, + }, + "visibility": { + Type: schema.TypeString, + Required: false, + Optional: true, + Default: "public", + Description: "Flag is this organisation should be publicly visible or not.", + }, + }, + Description: "`gitea_org` manages a gitea organisation.\n\n" + + "Organisations are a way to group repositories and abstract permission management in a gitea instance.", + } +} diff --git a/gitea/resource_gitea_public_key.go b/gitea/resource_gitea_public_key.go new file mode 100644 index 0000000..c83a39f --- /dev/null +++ b/gitea/resource_gitea_public_key.go @@ -0,0 +1,155 @@ +package gitea + +import ( + "fmt" + "strconv" + + "code.gitea.io/sdk/gitea" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +const ( + PublicKeyUser string = "username" + PublicKey string = "key" + PublicKeyReadOnlyFlag string = "read_only" + PublicKeyTitle string = "title" + PublicKeyId string = "id" + PublicKeyFingerprint string = "fingerprint" + PublicKeyCreated string = "created" + PublicKeyType string = "type" +) + +func resourcePublicKeyRead(d *schema.ResourceData, meta interface{}) (err error) { + client := meta.(*gitea.Client) + + id, err := strconv.ParseInt(d.Id(), 10, 64) + + var resp *gitea.Response + var pubKey *gitea.PublicKey + + pubKey, resp, err = client.GetPublicKey(id) + + if err != nil { + if resp.StatusCode == 404 { + d.SetId("") + return nil + } else { + return err + } + } + + err = setPublicKeyResourceData(pubKey, d) + + return +} + +func resourcePublicKeyCreate(d *schema.ResourceData, meta interface{}) (err error) { + client := meta.(*gitea.Client) + + var pubKey *gitea.PublicKey + + opts := gitea.CreateKeyOption{ + Title: d.Get(PublicKeyTitle).(string), + Key: d.Get(PublicKey).(string), + ReadOnly: d.Get(PublicKeyReadOnlyFlag).(bool), + } + + pubKey, _, err = client.AdminCreateUserPublicKey(d.Get(PublicKeyUser).(string), opts) + + err = setPublicKeyResourceData(pubKey, d) + + return +} + +func resourcePublicKeyUpdate(d *schema.ResourceData, meta interface{}) (err error) { + // update = recreate + resourcePublicKeyDelete(d, meta) + resourcePublicKeyCreate(d, meta) + return +} + +func resourcePublicKeyDelete(d *schema.ResourceData, meta interface{}) (err error) { + client := meta.(*gitea.Client) + + id, err := strconv.ParseInt(d.Id(), 10, 64) + + var resp *gitea.Response + + resp, err = client.AdminDeleteUserPublicKey(d.Get(PublicKeyUser).(string), int(id)) + + if err != nil { + if resp.StatusCode == 404 { + return + } else { + return err + } + } + + return +} + +func setPublicKeyResourceData(pubKey *gitea.PublicKey, d *schema.ResourceData) (err error) { + d.SetId(fmt.Sprintf("%d", pubKey.ID)) + d.Set(PublicKeyUser, d.Get(PublicKeyUser).(string)) + d.Set(PublicKey, d.Get(PublicKey).(string)) + d.Set(PublicKeyTitle, pubKey.Title) + d.Set(PublicKeyReadOnlyFlag, d.Get(PublicKeyReadOnlyFlag).(bool)) + d.Set(PublicKeyCreated, pubKey.Created) + d.Set(PublicKeyFingerprint, pubKey.Fingerprint) + d.Set(PublicKeyType, pubKey.KeyType) + return +} + +func resourceGiteaPublicKey() *schema.Resource { + return &schema.Resource{ + Read: resourcePublicKeyRead, + Create: resourcePublicKeyCreate, + Update: resourcePublicKeyUpdate, + Delete: resourcePublicKeyDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Schema: map[string]*schema.Schema{ + "title": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Title of the key to add", + }, + "key": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Sensitive: true, + Description: "An armored SSH key to add", + }, + "read_only": { + Type: schema.TypeBool, + Required: false, + Optional: true, + Default: false, + Description: "Describe if the key has only read access or read/write", + }, + "username": { + Type: schema.TypeString, + Required: true, + Optional: false, + ForceNew: true, + Description: "User to associate with the added key", + }, + "fingerprint": { + Type: schema.TypeString, + Computed: true, + }, + "created": { + Type: schema.TypeString, + Computed: true, + }, + "type": { + Type: schema.TypeString, + Computed: true, + }, + }, + Description: "`gitea_public_key` manages ssh key that are associated with users.", + } +} diff --git a/gitea/resource_gitea_repository.go b/gitea/resource_gitea_repository.go new file mode 100644 index 0000000..5cda6ef --- /dev/null +++ b/gitea/resource_gitea_repository.go @@ -0,0 +1,523 @@ +package gitea + +import ( + "fmt" + "strconv" + + "code.gitea.io/sdk/gitea" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +const ( + repoOwner string = "username" + repoName string = "name" + repoDescription string = "description" + repoPrivateFlag string = "private" + repoIssueLabels string = "issue_labels" + repoAutoInit string = "auto_init" + repoTemplate string = "repo_template" + repoGitignores string = "gitignores" + repoLicense string = "license" + repoReadme string = "readme" + repoDefaultBranch string = "default_branch" + repoWebsite string = "website" + repoIssues string = "has_issues" + repoWiki string = "has_wiki" + repoPrs string = "has_pull_requests" + repoProjects string = "has_projects" + repoIgnoreWhitespace string = "ignore_whitespace_conflicts" + repoAllowMerge string = "allow_merge_commits" + repoAllowRebase string = "allow_rebase" + repoAllowRebaseMerge string = "allow_rebase_explicit" + repoAllowSquash string = "allow_squash_merge" + repoAchived string = "archived" + repoAllowManualMerge string = "allow_manual_merge" + repoAutodetectManualMerge string = "autodetect_manual_merge" + repoMirror string = "mirror" + migrationCloneAddress string = "migration_clone_addresse" + migrationService string = "migration_service" + migrationServiceAuthName string = "migration_service_auth_username" + migrationServiceAuthPassword string = "migration_service_auth_password" + migrationServiceAuthToken string = "migration_service_auth_token" + migrationMilestones string = "migration_milestones" + migrationReleases string = "migration_releases" + migrationIssueLabels string = "migration_issue_labels" + migrationMirrorInterval string = "migration_mirror_interval" + migrationLFS string = "migration_lfs" + migrationLFSEndpoint string = "migration_lfs_endpoint" +) + +func resourceRepoRead(d *schema.ResourceData, meta interface{}) (err error) { + client := meta.(*gitea.Client) + + id, err := strconv.ParseInt(d.Id(), 10, 64) + var resp *gitea.Response + + if err != nil { + return err + } + + repo, resp, err := client.GetRepoByID(id) + + if err != nil { + if resp.StatusCode == 404 { + d.SetId("") + return nil + } else { + return err + } + } + + err = setRepoResourceData(repo, d) + + return +} + +func resourceRepoCreate(d *schema.ResourceData, meta interface{}) (err error) { + client := meta.(*gitea.Client) + + var repo *gitea.Repository + var resp *gitea.Response + var orgRepo bool + + _, resp, err = client.GetOrg(d.Get(repoOwner).(string)) + + if resp.StatusCode == 404 { + orgRepo = false + } else { + orgRepo = true + } + + if (d.Get(repoMirror)).(bool) { + opts := gitea.MigrateRepoOption{ + RepoName: d.Get(repoName).(string), + RepoOwner: d.Get(repoOwner).(string), + CloneAddr: d.Get(migrationCloneAddress).(string), + Service: gitea.GitServiceType(d.Get(migrationService).(string)), + Mirror: d.Get(repoMirror).(bool), + Private: d.Get(repoPrivateFlag).(bool), + Description: d.Get(repoDescription).(string), + Wiki: d.Get(repoWiki).(bool), + Milestones: d.Get(migrationMilestones).(bool), + Labels: d.Get(migrationIssueLabels).(bool), + Issues: d.Get(repoIssues).(bool), + PullRequests: d.Get(repoPrs).(bool), + Releases: d.Get(migrationReleases).(bool), + MirrorInterval: d.Get(migrationMirrorInterval).(string), + LFS: d.Get(migrationLFS).(bool), + LFSEndpoint: d.Get(migrationLFSEndpoint).(string), + } + + if d.Get(migrationServiceAuthName).(string) != "" { + opts.AuthUsername = d.Get(migrationServiceAuthName).(string) + } + if d.Get(migrationServiceAuthPassword).(string) != "" { + opts.AuthPassword = d.Get(migrationServiceAuthPassword).(string) + } + if d.Get(migrationServiceAuthToken).(string) != "" { + opts.AuthToken = d.Get(migrationServiceAuthToken).(string) + } + + repo, _, err = client.MigrateRepo(opts) + + } else { + opts := gitea.CreateRepoOption{ + Name: d.Get(repoName).(string), + Description: d.Get(repoDescription).(string), + Private: d.Get(repoPrivateFlag).(bool), + IssueLabels: d.Get(repoIssueLabels).(string), + AutoInit: d.Get(repoAutoInit).(bool), + Template: d.Get(repoTemplate).(bool), + Gitignores: d.Get(repoGitignores).(string), + License: d.Get(repoLicense).(string), + Readme: d.Get(repoReadme).(string), + DefaultBranch: d.Get(repoDefaultBranch).(string), + TrustModel: "default", + } + + if orgRepo { + repo, _, err = client.CreateOrgRepo(d.Get(repoOwner).(string), opts) + } else { + repo, _, err = client.CreateRepo(opts) + } + } + + if err != nil { + return + } + + err = setRepoResourceData(repo, d) + + return +} + +func resourceRepoUpdate(d *schema.ResourceData, meta interface{}) (err error) { + client := meta.(*gitea.Client) + + var repo *gitea.Repository + + var name string = d.Get(repoName).(string) + var description string = d.Get(repoDescription).(string) + var website string = d.Get(repoWebsite).(string) + var private bool = d.Get(repoPrivateFlag).(bool) + var template bool = d.Get(repoTemplate).(bool) + var hasIssues bool = d.Get(repoIssues).(bool) + var hasWiki bool = d.Get(repoWiki).(bool) + var defaultBranch string = d.Get(repoDefaultBranch).(string) + var hasPRs bool = d.Get(repoPrs).(bool) + var hasProjects bool = d.Get(repoProjects).(bool) + var ignoreWhitespaceConflicts bool = d.Get(repoIgnoreWhitespace).(bool) + var allowMerge bool = d.Get(repoAllowMerge).(bool) + var allowRebase bool = d.Get(repoAllowRebase).(bool) + var allowRebaseMerge bool = d.Get(repoAllowRebaseMerge).(bool) + var allowSquash bool = d.Get(repoAllowSquash).(bool) + var allowManualMerge bool = d.Get(repoAllowManualMerge).(bool) + var autodetectManualMerge bool = d.Get(repoAutodetectManualMerge).(bool) + + opts := gitea.EditRepoOption{ + Name: &name, + Description: &description, + Website: &website, + Private: &private, + Template: &template, + HasIssues: &hasIssues, + HasWiki: &hasWiki, + DefaultBranch: &defaultBranch, + HasPullRequests: &hasPRs, + HasProjects: &hasProjects, + IgnoreWhitespaceConflicts: &ignoreWhitespaceConflicts, + AllowMerge: &allowMerge, + AllowRebase: &allowRebase, + AllowRebaseMerge: &allowRebaseMerge, + AllowSquash: &allowSquash, + AllowManualMerge: &allowManualMerge, + AutodetectManualMerge: &autodetectManualMerge, + } + + if d.Get(repoMirror).(bool) { + var mirrorInterval string = d.Get(migrationMirrorInterval).(string) + opts.MirrorInterval = &mirrorInterval + } else { + var archived bool = d.Get(repoAchived).(bool) + opts.Archived = &archived + } + + repo, _, err = client.EditRepo(d.Get(repoOwner).(string), d.Get(repoName).(string), opts) + + if err != nil { + return err + } + err = setRepoResourceData(repo, d) + + return + +} + +func respurceRepoDelete(d *schema.ResourceData, meta interface{}) (err error) { + client := meta.(*gitea.Client) + + client.DeleteRepo(d.Get(repoOwner).(string), d.Get(repoName).(string)) + + return +} + +func setRepoResourceData(repo *gitea.Repository, d *schema.ResourceData) (err error) { + d.SetId(fmt.Sprintf("%d", repo.ID)) + d.Set("name", repo.Name) + d.Set("description", repo.Description) + d.Set("full_name", repo.FullName) + d.Set("private", repo.Private) + d.Set("fork", repo.Fork) + d.Set("mirror", repo.Mirror) + d.Set("size", repo.Size) + d.Set("html_url", repo.HTMLURL) + d.Set("ssh_url", repo.SSHURL) + d.Set("clone_url", repo.CloneURL) + d.Set("website", repo.Website) + d.Set("stars", repo.Stars) + d.Set("forks", repo.Forks) + d.Set("watchers", repo.Watchers) + d.Set("open_issue_count", repo.OpenIssues) + d.Set("default_branch", repo.DefaultBranch) + d.Set("created", repo.Created) + d.Set("updated", repo.Updated) + d.Set("permission_admin", repo.Permissions.Admin) + d.Set("permission_push", repo.Permissions.Push) + d.Set("permission_pull", repo.Permissions.Pull) + + return +} + +func resourceGiteaRepository() *schema.Resource { + return &schema.Resource{ + Read: resourceRepoRead, + Create: resourceRepoCreate, + Update: resourceRepoUpdate, + Delete: respurceRepoDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Schema: map[string]*schema.Schema{ + "username": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The Owner of the repository", + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The Name of the repository", + }, + "auto_init": { + Type: schema.TypeBool, + Required: false, + Optional: true, + Default: true, + Description: "Flag if the repository should be initiated with the configured values", + }, + "repo_template": { + Type: schema.TypeBool, + Required: false, + Optional: true, + Default: false, + }, + "issue_labels": { + Type: schema.TypeString, + Required: false, + Optional: true, + Default: "Default", + Description: "The Issue Label configuration to be used in this repository.\n" + + "Need to exist in the gitea instance", + }, + "gitignores": { + Type: schema.TypeString, + Required: false, + Optional: true, + Default: "", + Description: "A specific gitignore that should be commited to the repository" + + "on creation if `auto_init` is set to `true`\n" + + "Need to exist in the gitea instance", + }, + "license": { + Type: schema.TypeString, + Required: false, + Optional: true, + Default: "", + Description: "The license under which the source code of this repository should be.\n" + + "Need to exist in the gitea instance", + }, + "readme": { + Type: schema.TypeString, + Required: false, + Optional: true, + Default: "", + }, + "description": { + Type: schema.TypeString, + Required: false, + Optional: true, + Default: "", + Description: "The description of the repository.", + }, + "private": { + Type: schema.TypeBool, + Required: false, + Optional: true, + Default: true, + Description: "Flag if the repository should be private or not.", + }, + "default_branch": { + Type: schema.TypeString, + Required: false, + Optional: true, + Default: "main", + Description: "The default branch of the repository. Defaults to `main`", + }, + "created": { + Type: schema.TypeString, + Computed: true, + }, + "updated": { + Type: schema.TypeString, + Computed: true, + }, + "permission_admin": { + Type: schema.TypeBool, + Computed: true, + }, + "permission_push": { + Type: schema.TypeBool, + Computed: true, + }, + "permission_pull": { + Type: schema.TypeBool, + Computed: true, + }, + "website": { + Type: schema.TypeString, + Required: false, + Optional: true, + Default: "", + Description: "A link to a website with more information.", + }, + "has_issues": { + Type: schema.TypeBool, + Required: false, + Optional: true, + Default: true, + Description: "A flag if the repository should have issue management enabled or not.", + }, + "has_wiki": { + Type: schema.TypeBool, + Required: false, + Optional: true, + Default: true, + Description: "A flag if the repository should have the native wiki enabled or not.", + }, + "has_pull_requests": { + Type: schema.TypeBool, + Required: false, + Optional: true, + Default: true, + Description: "A flag if the repository should acceppt pull requests or not.", + }, + "has_projects": { + Type: schema.TypeBool, + Required: false, + Optional: true, + Default: true, + Description: "A flag if the repository should have the native project management enabled or not.", + }, + "ignore_whitespace_conflicts": { + Type: schema.TypeBool, + Required: false, + Optional: true, + Default: true, + }, + "allow_merge_commits": { + Type: schema.TypeBool, + Required: false, + Optional: true, + Default: true, + }, + "allow_rebase": { + Type: schema.TypeBool, + Required: false, + Optional: true, + Default: true, + }, + "allow_rebase_explicit": { + Type: schema.TypeBool, + Required: false, + Optional: true, + Default: true, + }, + "allow_squash_merge": { + Type: schema.TypeBool, + Required: false, + Optional: true, + Default: true, + }, + "archived": { + Type: schema.TypeBool, + Required: false, + Optional: true, + Default: false, + }, + "allow_manual_merge": { + Type: schema.TypeBool, + Required: false, + Optional: true, + Default: true, + }, + "autodetect_manual_merge": { + Type: schema.TypeBool, + Required: false, + Optional: true, + Default: true, + }, + "mirror": { + Type: schema.TypeBool, + Required: false, + Optional: true, + Default: false, + }, + "migration_clone_addresse": { + Type: schema.TypeString, + Required: false, + Optional: true, + ForceNew: true, + }, + "migration_service": { + Type: schema.TypeString, + Required: false, + ForceNew: true, + Optional: true, + Description: "git/github/gitlab/gitea/gogs", + }, + "migration_service_auth_username": { + Type: schema.TypeString, + Required: false, + Optional: true, + Default: "", + }, + "migration_service_auth_password": { + Type: schema.TypeString, + Required: false, + Optional: true, + Sensitive: true, + Default: "", + }, + "migration_service_auth_token": { + Type: schema.TypeString, + Required: false, + Optional: true, + Sensitive: true, + Default: "", + }, + "migration_milestones": { + Type: schema.TypeBool, + Required: false, + Optional: true, + Default: true, + }, + "migration_releases": { + Type: schema.TypeBool, + Required: false, + Optional: true, + Default: true, + }, + "migration_issue_labels": { + Type: schema.TypeBool, + Required: false, + Optional: true, + Default: true, + }, + "migration_mirror_interval": { + Type: schema.TypeString, + Required: false, + Optional: true, + Default: "8h0m0s", + Description: "valid time units are 'h', 'm', 's'. 0 to disable automatic sync", + }, + "migration_lfs": { + Type: schema.TypeBool, + Required: false, + Optional: true, + }, + "migration_lfs_endpoint": { + Type: schema.TypeString, + Required: false, + Optional: true, + Default: "", + }, + }, + Description: "`gitea_repository` manages a gitea repository.\n\n" + + "Per default this repository will be initializiled with the provided configuration (gitignore, License etc.).\n" + + "If the `username` property is set to a organisation name, the provider will try to look if this organisation exists " + + "and create the repository under the organisation scope.\n\n" + + "Repository migrations have some properties that are not available to regular repositories. These are all prefixed with `migration_`.", + } +} diff --git a/gitea/resource_gitea_team.go b/gitea/resource_gitea_team.go new file mode 100644 index 0000000..8bfed82 --- /dev/null +++ b/gitea/resource_gitea_team.go @@ -0,0 +1,296 @@ +package gitea + +import ( + "fmt" + "strconv" + "strings" + + "code.gitea.io/sdk/gitea" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +const ( + TeamName string = "name" + TeamOrg string = "organisation" + TeamDescription string = "description" + TeamPermissions string = "permission" + TeamCreateRepoFlag string = "can_create_repos" + TeamIncludeAllReposFlag string = "include_all_repositories" + TeamUnits string = "units" + TeamMembers string = "members" +) + +func resourceTeamRead(d *schema.ResourceData, meta interface{}) (err error) { + client := meta.(*gitea.Client) + + id, err := strconv.ParseInt(d.Id(), 10, 64) + + var resp *gitea.Response + var team *gitea.Team + + team, resp, err = client.GetTeam(id) + + if err != nil { + if resp.StatusCode == 404 { + d.SetId("") + return nil + } else { + return err + } + } + + err = setTeamResourceData(team, d) + + return +} + +func resourceTeamCreate(d *schema.ResourceData, meta interface{}) (err error) { + client := meta.(*gitea.Client) + + var team *gitea.Team + var units []gitea.RepoUnitType + + if strings.Contains(d.Get(TeamUnits).(string), "repo.code") { + units = append(units, gitea.RepoUnitCode) + } + if strings.Contains(d.Get(TeamUnits).(string), "repo.issues") { + units = append(units, gitea.RepoUnitIssues) + } + if strings.Contains(d.Get(TeamUnits).(string), "repo.ext_issues") { + units = append(units, gitea.RepoUnitExtIssues) + } + if strings.Contains(d.Get(TeamUnits).(string), "repo.wiki") { + units = append(units, gitea.RepoUnitWiki) + } + if strings.Contains(d.Get(TeamUnits).(string), "repo.pulls") { + units = append(units, gitea.RepoUnitPulls) + } + if strings.Contains(d.Get(TeamUnits).(string), "repo.releases") { + units = append(units, gitea.RepoUnitReleases) + } + if strings.Contains(d.Get(TeamUnits).(string), "repo.ext_wiki") { + units = append(units, gitea.RepoUnitExtWiki) + } + if strings.Contains(d.Get(TeamUnits).(string), "repo.projects") { + units = append(units, gitea.RepoUnitProjects) + } + + opts := gitea.CreateTeamOption{ + Name: d.Get(TeamName).(string), + Description: d.Get(TeamDescription).(string), + Permission: gitea.AccessMode(d.Get(TeamPermissions).(string)), + CanCreateOrgRepo: d.Get(TeamCreateRepoFlag).(bool), + IncludesAllRepositories: d.Get(TeamIncludeAllReposFlag).(bool), + Units: units, + } + + team, _, err = client.CreateTeam(d.Get(TeamOrg).(string), opts) + + if err != nil { + return + } + + users := d.Get(TeamMembers).([]interface{}) + + for _, user := range users { + if user != "" { + _, err = client.AddTeamMember(team.ID, user.(string)) + if err != nil { + return err + } + } + } + + err = setTeamResourceData(team, d) + + return +} + +func resourceTeamUpdate(d *schema.ResourceData, meta interface{}) (err error) { + client := meta.(*gitea.Client) + + id, err := strconv.ParseInt(d.Id(), 10, 64) + + var resp *gitea.Response + var team *gitea.Team + + team, resp, err = client.GetTeam(id) + + if err != nil { + if resp.StatusCode == 404 { + resourceTeamCreate(d, meta) + } else { + return err + } + } + + description := d.Get(TeamDescription).(string) + canCreateRepo := d.Get(TeamCreateRepoFlag).(bool) + includeAllRepos := d.Get(TeamIncludeAllReposFlag).(bool) + + var units []gitea.RepoUnitType + + if strings.Contains(d.Get(TeamUnits).(string), "repo.code") { + units = append(units, gitea.RepoUnitCode) + } + if strings.Contains(d.Get(TeamUnits).(string), "repo.issues") { + units = append(units, gitea.RepoUnitIssues) + } + if strings.Contains(d.Get(TeamUnits).(string), "repo.ext_issues") { + units = append(units, gitea.RepoUnitExtIssues) + } + if strings.Contains(d.Get(TeamUnits).(string), "repo.wiki") { + units = append(units, gitea.RepoUnitWiki) + } + if strings.Contains(d.Get(TeamUnits).(string), "repo.pulls") { + units = append(units, gitea.RepoUnitPulls) + } + if strings.Contains(d.Get(TeamUnits).(string), "repo.releases") { + units = append(units, gitea.RepoUnitReleases) + } + if strings.Contains(d.Get(TeamUnits).(string), "repo.ext_wiki") { + units = append(units, gitea.RepoUnitExtWiki) + } + if strings.Contains(d.Get(TeamUnits).(string), "repo.projects") { + units = append(units, gitea.RepoUnitProjects) + } + + opts := gitea.EditTeamOption{ + Name: d.Get(TeamName).(string), + Description: &description, + Permission: gitea.AccessMode(d.Get(TeamPermissions).(string)), + CanCreateOrgRepo: &canCreateRepo, + IncludesAllRepositories: &includeAllRepos, + Units: units, + } + + resp, err = client.EditTeam(id, opts) + + if err != nil { + return err + } + + users := d.Get(TeamMembers).([]interface{}) + + for _, user := range users { + if user != "" { + _, err = client.AddTeamMember(team.ID, user.(string)) + if err != nil { + return err + } + } + } + + team, _, _ = client.GetTeam(id) + + err = setTeamResourceData(team, d) + + return +} + +func resourceTeamDelete(d *schema.ResourceData, meta interface{}) (err error) { + client := meta.(*gitea.Client) + + id, err := strconv.ParseInt(d.Id(), 10, 64) + + var resp *gitea.Response + + resp, err = client.DeleteTeam(id) + + if err != nil { + if resp.StatusCode == 404 { + return + } else { + return err + } + } + + return +} + +func setTeamResourceData(team *gitea.Team, d *schema.ResourceData) (err error) { + d.SetId(fmt.Sprintf("%d", team.ID)) + d.Set(TeamCreateRepoFlag, team.CanCreateOrgRepo) + d.Set(TeamDescription, team.Description) + d.Set(TeamName, team.Name) + d.Set(TeamPermissions, string(team.Permission)) + d.Set(TeamIncludeAllReposFlag, team.IncludesAllRepositories) + d.Set(TeamUnits, d.Get(TeamUnits).(string)) + d.Set(TeamOrg, d.Get(TeamOrg).(string)) + d.Set(TeamMembers, d.Get(TeamMembers)) + return +} + +func resourceGiteaTeam() *schema.Resource { + return &schema.Resource{ + Read: resourceTeamRead, + Create: resourceTeamCreate, + Update: resourceTeamUpdate, + Delete: resourceTeamDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Name of the Team", + }, + "organisation": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The organisation which this Team is part of.", + }, + "description": { + Type: schema.TypeString, + Required: false, + Optional: true, + Default: "", + Description: "Description of the Team", + }, + "permission": { + Type: schema.TypeString, + Required: false, + Optional: true, + Default: "", + Description: "Permissions associated with this Team\n" + + "Can be `none`, `read`, `write`, `admin` or `owner`", + }, + "can_create_repos": { + Type: schema.TypeBool, + Required: false, + Optional: true, + Default: true, + Description: "Flag if the Teams members should be able to create Rpositories in the Organisation", + }, + "include_all_repositories": { + Type: schema.TypeBool, + Required: false, + Optional: true, + Default: true, + Description: "Flag if the Teams members should have access to all Repositories in the Organisation", + }, + "units": { + Type: schema.TypeString, + Required: false, + Optional: true, + Default: "[repo.code, repo.issues, repo.ext_issues, repo.wiki, repo.pulls, repo.releases, repo.projects, repo.ext_wiki]", + Description: "List of types of Repositories that should be allowed to be created from Team members.\n" + + "Can be `repo.code`, `repo.issues`, `repo.ext_issues`, `repo.wiki`, `repo.pulls`, `repo.releases`, `repo.projects` and/or `repo.ext_wiki`", + }, + "members": { + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Optional: true, + Required: false, + Computed: true, + Description: "List of Users that should be part of this team", + }, + }, + Description: "`gitea_team` manages Team that are part of an organisation.", + } +} diff --git a/gitea/resource_gitea_user.go b/gitea/resource_gitea_user.go new file mode 100644 index 0000000..432b24e --- /dev/null +++ b/gitea/resource_gitea_user.go @@ -0,0 +1,364 @@ +package gitea + +import ( + "fmt" + "strconv" + + "code.gitea.io/sdk/gitea" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +const ( + userName string = "username" + userLoginName string = "login_name" + userEmail string = "email" + userFullName string = "full_name" + userPassword string = "password" + userMustChangePassword string = "must_change_password" + userSendNotification string = "send_notification" + userVisibility string = "visibility" + userDescription string = "description" + userLocation string = "location" + userActive string = "active" + userAdmin string = "admin" + userAllowGitHook string = "allow_git_hook" + userAllowLocalImport string = "allow_import_local" + userMaxRepoCreation string = "max_repo_creation" + userPhorbitLogin string = "prohibit_login" + userAllowCreateOrgs string = "allow_create_organization" + userRestricted string = "restricted" + userForcePasswordChange string = "force_password_change" +) + +func resourceUserRead(d *schema.ResourceData, meta interface{}) (err error) { + client := meta.(*gitea.Client) + + id, err := strconv.ParseInt(d.Id(), 10, 64) + + var resp *gitea.Response + var user *gitea.User + + user, resp, err = client.GetUserByID(id) + + if err != nil { + if resp.StatusCode == 404 { + d.SetId("") + return nil + } else { + return err + } + } + + err = setUserResourceData(user, d) + + return +} + +func resourceUserCreate(d *schema.ResourceData, meta interface{}) (err error) { + client := meta.(*gitea.Client) + + var user *gitea.User + visibility := gitea.VisibleType(d.Get(userVisibility).(string)) + changePassword := d.Get(userMustChangePassword).(bool) + + opts := gitea.CreateUserOption{ + SourceID: 0, + LoginName: d.Get(userLoginName).(string), + Username: d.Get(userName).(string), + FullName: d.Get(userFullName).(string), + Email: d.Get(userEmail).(string), + Password: d.Get(userPassword).(string), + MustChangePassword: &changePassword, + SendNotify: d.Get(userSendNotification).(bool), + Visibility: &visibility, + } + + user, _, err = client.AdminCreateUser(opts) + if err != nil { + return + } + + d.SetId(fmt.Sprintf("%d", user.ID)) + + err = resourceUserUpdate(d, meta) + + return +} + +func resourceUserUpdate(d *schema.ResourceData, meta interface{}) (err error) { + client := meta.(*gitea.Client) + + id, err := strconv.ParseInt(d.Id(), 10, 64) + var resp *gitea.Response + var user *gitea.User + + user, resp, err = client.GetUserByID(id) + + if err != nil { + if resp.StatusCode == 404 { + resourceUserCreate(d, meta) + } else { + return err + } + } + + mail := d.Get(userEmail).(string) + fullName := d.Get(userFullName).(string) + description := d.Get(userDescription).(string) + changePassword := d.Get(userMustChangePassword).(bool) + location := d.Get(userLocation).(string) + active := d.Get(userActive).(bool) + admin := d.Get(userAdmin).(bool) + allowHook := d.Get(userAllowGitHook).(bool) + allowImport := d.Get(userAllowLocalImport).(bool) + maxRepoCreation := d.Get(userMaxRepoCreation).(int) + accessDenied := d.Get(userPhorbitLogin).(bool) + allowOrgs := d.Get(userAllowCreateOrgs).(bool) + restricted := d.Get(userRestricted).(bool) + visibility := gitea.VisibleType(d.Get(userVisibility).(string)) + + if d.Get(userForcePasswordChange).(bool) { + opts := gitea.EditUserOption{ + SourceID: 0, + LoginName: d.Get(userLoginName).(string), + Email: &mail, + FullName: &fullName, + Password: d.Get(userPassword).(string), + Description: &description, + MustChangePassword: &changePassword, + Location: &location, + Active: &active, + Admin: &admin, + AllowGitHook: &allowHook, + AllowImportLocal: &allowImport, + MaxRepoCreation: &maxRepoCreation, + ProhibitLogin: &accessDenied, + AllowCreateOrganization: &allowOrgs, + Restricted: &restricted, + Visibility: &visibility, + } + _, err = client.AdminEditUser(d.Get(userName).(string), opts) + + if err != nil { + return err + } + + } else { + opts := gitea.EditUserOption{ + SourceID: 0, + LoginName: d.Get(userLoginName).(string), + Email: &mail, + FullName: &fullName, + Description: &description, + MustChangePassword: &changePassword, + Location: &location, + Active: &active, + Admin: &admin, + AllowGitHook: &allowHook, + AllowImportLocal: &allowImport, + MaxRepoCreation: &maxRepoCreation, + ProhibitLogin: &accessDenied, + AllowCreateOrganization: &allowOrgs, + Restricted: &restricted, + Visibility: &visibility, + } + _, err = client.AdminEditUser(d.Get(userName).(string), opts) + + if err != nil { + return err + } + } + + user, _, err = client.GetUserByID(id) + + err = setUserResourceData(user, d) + + return +} + +func resourceUserDelete(d *schema.ResourceData, meta interface{}) (err error) { + client := meta.(*gitea.Client) + + var resp *gitea.Response + + resp, err = client.AdminDeleteUser(d.Get(userName).(string)) + + if err != nil { + if resp.StatusCode == 404 { + return + } else { + return err + } + } + + return +} + +func setUserResourceData(user *gitea.User, d *schema.ResourceData) (err error) { + d.SetId(fmt.Sprintf("%d", user.ID)) + d.Set(userName, user.UserName) + d.Set(userEmail, user.Email) + d.Set(userFullName, user.FullName) + d.Set(userAdmin, user.IsAdmin) + d.Set("created", user.Created) + d.Set("avatar_url", user.AvatarURL) + d.Set("last_login", user.LastLogin) + d.Set("language", user.Language) + d.Set(userLoginName, d.Get(userLoginName).(string)) + d.Set(userMustChangePassword, d.Get(userMustChangePassword).(bool)) + d.Set(userSendNotification, d.Get(userSendNotification).(bool)) + d.Set(userVisibility, d.Get(userVisibility).(string)) + d.Set(userDescription, d.Get(userDescription).(string)) + d.Set(userLocation, d.Get(userLocation).(string)) + d.Set(userActive, d.Get(userActive).(bool)) + d.Set(userAllowGitHook, d.Get(userAllowGitHook).(bool)) + d.Set(userAllowLocalImport, d.Get(userAllowLocalImport).(bool)) + d.Set(userMaxRepoCreation, d.Get(userMaxRepoCreation).(int)) + d.Set(userPhorbitLogin, d.Get(userPhorbitLogin).(bool)) + d.Set(userAllowCreateOrgs, d.Get(userAllowCreateOrgs).(bool)) + d.Set(userRestricted, d.Get(userRestricted).(bool)) + d.Set(userForcePasswordChange, d.Get(userForcePasswordChange).(bool)) + + return +} + +func resourceGiteaUser() *schema.Resource { + return &schema.Resource{ + Read: resourceUserRead, + Create: resourceUserCreate, + Update: resourceUserUpdate, + Delete: resourceUserDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Schema: map[string]*schema.Schema{ + "username": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Username of the user to be created", + }, + "login_name": { + Type: schema.TypeString, + Optional: false, + Required: true, + Description: "The login name can differ from the username", + }, + "email": { + Type: schema.TypeString, + Optional: false, + Required: true, + Description: "E-Mail Address of the user", + }, + "full_name": { + Type: schema.TypeString, + Computed: true, + Optional: true, + Required: false, + Description: "Full name of the user", + }, + "password": { + Type: schema.TypeString, + Optional: false, + Required: true, + Sensitive: true, + Description: "Password to be set for the user", + }, + "must_change_password": { + Type: schema.TypeBool, + Optional: true, + Required: false, + Default: true, + Description: "Flag if the user should change the password after first login", + }, + "send_notification": { + Type: schema.TypeBool, + Optional: true, + Required: false, + Default: true, + Description: "Flag to send a notification about the user creation to the defined `email`", + }, + "visibility": { + Type: schema.TypeString, + Optional: true, + Required: false, + Default: "public", + Description: "Visibility of the user. Can be `public`, `limited` or `private`", + }, + "description": { + Type: schema.TypeString, + Optional: true, + Required: false, + Default: "", + Description: "A description of the user", + }, + "location": { + Type: schema.TypeString, + Optional: true, + Required: false, + Default: "", + }, + "active": { + Type: schema.TypeBool, + Optional: true, + Required: false, + Default: true, + Description: "Flag if this user should be active or not", + }, + "admin": { + Type: schema.TypeBool, + Optional: true, + Required: false, + Default: false, + Description: "Flag if this user should be an administrator or not", + }, + "allow_git_hook": { + Type: schema.TypeBool, + Optional: true, + Required: false, + Default: true, + }, + "allow_import_local": { + Type: schema.TypeBool, + Optional: true, + Required: false, + Default: true, + }, + "max_repo_creation": { + Type: schema.TypeInt, + Optional: true, + Required: false, + Default: -1, + }, + "prohibit_login": { + Type: schema.TypeBool, + Optional: true, + Required: false, + Default: false, + Description: "Flag if the user should not be allowed to log in (bot user)", + }, + "allow_create_organization": { + Type: schema.TypeBool, + Optional: true, + Required: false, + Default: true, + }, + "restricted": { + Type: schema.TypeBool, + Optional: true, + Required: false, + Default: false, + }, + "force_password_change": { + Type: schema.TypeBool, + Optional: true, + Required: false, + Default: false, + Description: "Flag if the user defined password should be overwritten or not", + }, + }, + Description: "`gitea_user` manages a native gitea user.\n\n" + + "If you are using OIDC or other kinds of authentication mechanisms you can still try to manage" + + "ssh keys or other ressources this way", + } +}