github.com/crossplane/upjet@v1.3.0/docs/generating-a-provider.md (about) 1 <!-- 2 SPDX-FileCopyrightText: 2023 The Crossplane Authors <https://crossplane.io> 3 4 SPDX-License-Identifier: CC-BY-4.0 5 --> 6 # Generating a Crossplane provider 7 8 This guide shows you how to generate a Crossplane provider based on an existing 9 Terraform provider using Upjet. The guide uses the [Terraform GitHub provider] 10 as the example, but the process is similar for any other Terraform provider. 11 12 ## Prepare your new provider repository 13 14 1. Create a new GitHub repository for the Crossplane provider by clicking the 15 "**Use this template**" button in the [upjet-provider-template] repository. The 16 expected repository name is in the format `provider-<name>`. For example, 17 `provider-github`. The script in step 3 expects this format and fails if you 18 follow a different naming convention. 19 1. Clone the repository to your local environment and `cd` into the repository 20 directory. 21 1. Fetch the [upbound/build] submodule by running the following 22 command: 23 24 ```bash 25 make submodules 26 ``` 27 28 1. To setup your provider name and group run the `./hack/prepare.sh` 29 script from the repository root to prepare the code. 30 31 ```bash 32 ./hack/prepare.sh 33 ``` 34 35 1. Ensure your organization name is correct in the `Makefile` for the 36 `PROJECT_REPO` variable. 37 1. To configure which Terraform provider to generate from, update the following 38 variables in the `Makefile`: 39 40 | Variable | Description | 41 | -------- | ----------- | 42 | `TERRAFORM_PROVIDER_SOURCE` | Find this variable on the Terraform registry for the provider. You can see the source value when clicking on the "`USE PROVIDER`" dropdown button in the navigation. | 43 |`TERRAFORM_PROVIDER_REPO` | The URL to the repository that hosts the provider's code. | 44 | `TERRAFORM_PROVIDER_VERSION` | Find this variable on the Terraform registry for the provider. You can see the source value when clicking on the "`USE PROVIDER`" dropdown button in the navigation. | 45 |`TERRAFORM_PROVIDER_DOWNLOAD_NAME` | The name of the provider in the [Terraform registry](https://releases.hashicorp.com/) | 46 |`TERRAFORM_NATIVE_PROVIDER_BINARY` | The name of the binary in the Terraform provider. This follows the pattern `terraform-provider-{provider name}_v{provider version}`. | 47 |`TERRAFORM_DOCS_PATH` | The relative path, from the root of the repository, where the provider resource documentation exist. | 48 49 For example, for the [Terraform GitHub provider], the variables are: 50 51 ```makefile 52 export TERRAFORM_PROVIDER_SOURCE := integrations/github 53 export TERRAFORM_PROVIDER_REPO := https://github.com/integrations/terraform-provider-github 54 export TERRAFORM_PROVIDER_VERSION := 5.32.0 55 export TERRAFORM_PROVIDER_DOWNLOAD_NAME := terraform-provider-github 56 export TERRAFORM_NATIVE_PROVIDER_BINARY := terraform-provider-github_v5.32.0 57 export TERRAFORM_DOCS_PATH := website/docs/r 58 ``` 59 60 Refer to [the Dockerfile](https://github.com/upbound/upjet-provider-template/blob/main/cluster/images/upjet-provider-template/Dockerfile) to see the variables called when building the provider. 61 62 ## Configure the provider resources 63 64 1. First you need to add the `ProviderConfig` logic. 65 - In `upjet-provider-template`, there is 66 already boilerplate code in the file `internal/clients/github.go` which takes 67 care of fetching secret data referenced from the `ProviderConfig` resource. 68 - Reference the [Terraform Github provider] documentation for information on 69 authentication and provide the necessary keys.: 70 71 ```go 72 const ( 73 ... 74 keyBaseURL = "base_url" 75 keyOwner = "owner" 76 keyToken = "token" 77 ) 78 ``` 79 80 ```go 81 func TerraformSetupBuilder(version, providerSource, providerVersion string) terraform.SetupFn { 82 ... 83 // set provider configuration 84 ps.Configuration = map[string]any{} 85 if v, ok := creds[keyBaseURL]; ok { 86 ps.Configuration[keyBaseURL] = v 87 } 88 if v, ok := creds[keyOwner]; ok { 89 ps.Configuration[keyOwner] = v 90 } 91 if v, ok := creds[keyToken]; ok { 92 ps.Configuration[keyToken] = v 93 } 94 return ps, nil 95 } 96 ``` 97 98 1. Next add external name configurations for the [github_repository] and 99 [github_branch] Terraform resources. 100 101 > [!NOTE] 102 > Only generate resources with an external name configuration defined. 103 104 - Add external name configurations for these two resources in 105 `config/external_name.go` as an entry to the map called 106 `ExternalNameConfigs` 107 108 ```go 109 // ExternalNameConfigs contains all external name configurations for this 110 // provider. 111 var ExternalNameConfigs = map[string]config.ExternalName{ 112 ... 113 // Name is a parameter and it is also used to import the resource. 114 "github_repository": config.NameAsIdentifier, 115 // The import ID consists of several parameters. We'll use branch name as 116 // the external name. 117 "github_branch": config.TemplatedStringAsIdentifier("branch", "{{ .parameters.repository }}:{{ .external_name }}:{{ .parameters.source_branch }}"), 118 } 119 ``` 120 121 - Take a look at the documentation for configuring a resource for more 122 information about [external name configuration](configuring-a-resource.md#external-name). 123 124 1. Next add custom configurations for these two resources as follows: 125 126 - Create custom configuration directory for whole repository group 127 128 ```bash 129 mkdir config/repository 130 ``` 131 132 - Create custom configuration directory for whole branch group 133 134 ```bash 135 mkdir config/branch 136 ``` 137 138 - Create the repository group configuration file 139 140 ```bash 141 cat <<EOF > config/repository/config.go 142 package repository 143 144 import "github.com/crossplane/upjet/pkg/config" 145 146 // Configure configures individual resources by adding custom ResourceConfigurators. 147 func Configure(p *config.Provider) { 148 p.AddResourceConfigurator("github_repository", func(r *config.Resource) { 149 // We need to override the default group that upjet generated for 150 // this resource, which would be "github" 151 r.ShortGroup = "repository" 152 }) 153 } 154 EOF 155 ``` 156 157 - Create the branch group configuration file 158 159 > [!NOTE] 160 > Note that you need to change `myorg/provider-github` to your organization. 161 162 ```bash 163 cat <<EOF > config/branch/config.go 164 package branch 165 166 import "github.com/crossplane/upjet/pkg/config" 167 168 func Configure(p *config.Provider) { 169 p.AddResourceConfigurator("github_branch", func(r *config.Resource) { 170 // We need to override the default group that upjet generated for 171 // this resource, which would be "github" 172 r.ShortGroup = "branch" 173 174 // This resource need the repository in which branch would be created 175 // as an input. And by defining it as a reference to Repository 176 // object, we can build cross resource referencing. See 177 // repositoryRef in the example in the Testing section below. 178 r.References["repository"] = config.Reference{ 179 Type: "github.com/myorg/provider-github/apis/repository/v1alpha1.Repository", 180 } 181 }) 182 } 183 EOF 184 ``` 185 186 And register custom configurations in `config/provider.go`: 187 188 ```diff 189 import ( 190 ... 191 192 ujconfig "github.com/upbound/crossplane/pkg/config" 193 194 - "github.com/myorg/provider-github/config/null" 195 + "github.com/myorg/provider-github/config/branch" 196 + "github.com/myorg/provider-github/config/repository" 197 ) 198 199 func GetProvider() *ujconfig.Provider { 200 ... 201 for _, configure := range []func(provider *ujconfig.Provider){ 202 // add custom config functions 203 - null.Configure, 204 + repository.Configure, 205 + branch.Configure, 206 } { 207 configure(pc) 208 } 209 ``` 210 211 _To learn more about custom resource configurations (in step 7), please 212 see the [Configuring a Resource](configuring-a-resource.md) document._ 213 214 1. Now we can generate our Upjet Provider: 215 216 Before we run `make generate` ensure to install `goimports` 217 218 ```bash 219 go install golang.org/x/tools/cmd/goimports@latest 220 ``` 221 222 ```bash 223 make generate 224 ``` 225 226 ## Testing the generated resources 227 228 Now let's test our generated resources. 229 230 1. First, we will create example resources under the `examples` directory: 231 232 Create example directories for repository and branch groups: 233 234 ```bash 235 mkdir examples/repository 236 mkdir examples/branch 237 238 # remove the sample directory which was an example in the template 239 rm -rf examples/null 240 ``` 241 242 Create a provider secret template: 243 244 ```bash 245 cat <<EOF > examples/providerconfig/secret.yaml.tmpl 246 apiVersion: v1 247 kind: Secret 248 metadata: 249 name: example-creds 250 namespace: crossplane-system 251 type: Opaque 252 stringData: 253 credentials: | 254 { 255 "token": "y0ur-t0k3n" 256 } 257 EOF 258 ``` 259 260 Create example for `repository` resource, which will use 261 `upjet-provider-template` repo as template for the repository to be created: 262 263 ```bash 264 cat <<EOF > examples/repository/repository.yaml 265 apiVersion: repository.github.upbound.io/v1alpha1 266 kind: Repository 267 metadata: 268 name: hello-crossplane 269 spec: 270 forProvider: 271 description: "Managed with Crossplane Github Provider (generated with Upjet)" 272 visibility: public 273 template: 274 - owner: upbound 275 repository: upjet-provider-template 276 providerConfigRef: 277 name: default 278 EOF 279 ``` 280 281 Create `branch` resource which refers to the above repository managed 282 resource: 283 284 ```bash 285 cat <<EOF > examples/branch/branch.yaml 286 apiVersion: branch.github.upbound.io/v1alpha1 287 kind: Branch 288 metadata: 289 name: hello-upjet 290 spec: 291 forProvider: 292 repositoryRef: 293 name: hello-crossplane 294 providerConfigRef: 295 name: default 296 EOF 297 ``` 298 299 In order to change the `apiVersion`, you can use `WithRootGroup` and 300 `WithShortName` options in `config/provider.go` as arguments to 301 `ujconfig.NewProvider`. 302 303 2. Generate a [Personal Access Token](https://github.com/settings/tokens) for 304 your Github account with `repo/public_repo` and `delete_repo` scopes. 305 306 3. Create `examples/providerconfig/secret.yaml` from 307 `examples/providerconfig/secret.yaml.tmpl` and set your token in the file: 308 309 ```bash 310 GITHUB_TOKEN=<your-token-here> 311 cat examples/providerconfig/secret.yaml.tmpl | sed -e "s/y0ur-t0k3n/${GITHUB_TOKEN}/g" > examples/providerconfig/secret.yaml 312 ``` 313 314 4. Apply CRDs: 315 316 ```bash 317 kubectl apply -f package/crds 318 ``` 319 320 5. Run the provider: 321 322 Please make sure Terraform is installed before running the "make run" 323 command, you can check 324 [this guide](https://developer.hashicorp.com/terraform/downloads). 325 326 ```bash 327 make run 328 ``` 329 330 6. Apply ProviderConfig and example manifests (_In another terminal since the 331 previous command is blocking_): 332 333 ```bash 334 # Create "crossplane-system" namespace if not exists 335 kubectl create namespace crossplane-system --dry-run=client -o yaml | kubectl apply -f - 336 337 kubectl apply -f examples/providerconfig/ 338 kubectl apply -f examples/repository/repository.yaml 339 kubectl apply -f examples/branch/branch.yaml 340 ``` 341 342 7. Observe managed resources and wait until they are ready: 343 344 ```bash 345 watch kubectl get managed 346 ``` 347 348 ```bash 349 NAME READY SYNCED EXTERNAL-NAME AGE 350 branch.branch.github.jet.crossplane.io/hello-upjet True True hello-crossplane:hello-upjet 89s 351 352 NAME READY SYNCED EXTERNAL-NAME AGE 353 repository.repository.github.jet.crossplane.io/hello-crossplane True True hello-crossplane 89s 354 ``` 355 356 Verify that repo `hello-crossplane` and branch `hello-upjet` created under 357 your GitHub account. 358 359 8. You can check the errors and events by calling `kubectl describe` for either 360 of the resources. 361 362 9. Cleanup 363 364 ```bash 365 kubectl delete -f examples/branch/branch.yaml 366 kubectl delete -f examples/repository/repository.yaml 367 ``` 368 369 Verify that the repo got deleted once deletion is completed on the control 370 plane. 371 372 ## Next steps 373 374 Now that you've seen the basics of generating `CustomResourceDefinitions` for 375 your provider, you can learn more about 376 [configuring resources](configuring-a-resource.md) or 377 [testing your resources](testing-with-uptest.md) with Uptest. 378 379 [Terraform GitHub provider]: https://registry.terraform.io/providers/integrations/github/latest/docs 380 [upjet-provider-template]: https://github.com/upbound/upjet-provider-template 381 [upbound/build]: https://github.com/upbound/build 382 [github_repository]: https://registry.terraform.io/providers/integrations/github/latest/docs/resources/repository 383 [github_branch]: https://registry.terraform.io/providers/integrations/github/latest/docs/resources/branch