Skip to content

Commit

Permalink
azuredevops: new provider (GoogleCloudPlatform#1054)
Browse files Browse the repository at this point in the history
* azuredevops: scafolding

* azuredevops: fixes for a minimal working version

* azuredevops: get GetResourceConnections per service

* azuredevops: added azuredevops

* azuredevops: simplified services impl

* azuredevops: added group

* azuredevops: added docs

* azuredevops: fix ci lints

* azuredevops: silence critic
  • Loading branch information
juarezr authored Sep 17, 2021
1 parent f7bd3be commit 4f25129
Show file tree
Hide file tree
Showing 11 changed files with 425 additions and 0 deletions.
45 changes: 45 additions & 0 deletions cmd/provider_cmd_azuredevops.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright 2019 The Terraformer Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd

import (
azuredevops "github.com/GoogleCloudPlatform/terraformer/providers/azuredevops"

"github.com/GoogleCloudPlatform/terraformer/terraformutils"
"github.com/spf13/cobra"
)

func newCmdAzureDevOpsImporter(options ImportOptions) *cobra.Command {

cmd := &cobra.Command{
Use: "azuredevops",
Short: "Import current state to Terraform configuration from Azure DevOps",
Long: "Import current state to Terraform configuration from Azure DevOps",
RunE: func(cmd *cobra.Command, args []string) error {
provider := newAzureDevOpsProvider()
err := Import(provider, options, []string{options.ResourceGroup})
if err != nil {
return err
}
return nil
},
}
cmd.AddCommand(listCmd(newAzureDevOpsProvider()))
baseProviderFlags(cmd.PersistentFlags(), &options, "project,team,git", "project=name1:name2:name3")
return cmd
}

func newAzureDevOpsProvider() terraformutils.ProviderGenerator {
return &azuredevops.AzureDevOpsProvider{}
}
2 changes: 2 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ func providerImporterSubcommands() []func(options ImportOptions) *cobra.Command
newCmdNs1Importer,
newCmdPanosImporter,
// VCS
newCmdAzureDevOpsImporter,
newCmdGithubImporter,
newCmdGitLabImporter,
// Monitoring & System Management
Expand Down Expand Up @@ -108,6 +109,7 @@ func providerGenerators() map[string]func() terraformutils.ProviderGenerator {
// Network
newCloudflareProvider,
// VCS
newAzureDevOpsProvider,
newGitHubProvider,
newGitLabProvider,
// Monitoring & System Management
Expand Down
26 changes: 26 additions & 0 deletions docs/azuredevops.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Use with Azure DevOps

Supports acess via [Personal Access Token](https://registry.terraform.io/providers/microsoft/azuredevops/latest/docs/guides/authenticating_using_the_personal_access_token).

## Example

``` sh
export AZDO_ORG_SERVICE_URL="https://dev.azure.com/<Your Org Name>"
export AZDO_PERSONAL_ACCESS_TOKEN="<Personal Access Token>"

./terraformer import azuredevops -r *
./terraformer import azuredevops -r project,git_repository
```

## List of supported Azure resources

* `project`
* `azuredevops_project`
* `group`
* `azuredevops_group`
* `git_repository`
* `azuredevops_git_repository`

## Notes

Since [Terraform Provider for Azure DevOps](https://github.com/microsoft/terraform-provider-azuredevops) `version 0.17`.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ require (
github.com/jonboydell/logzio_client v1.2.0
github.com/labd/commercetools-go-sdk v0.3.1
github.com/linode/linodego v0.24.1
github.com/microsoft/azure-devops-go-api/azuredevops v1.0.0-b5
github.com/mrparkers/terraform-provider-keycloak v0.0.0-20200506151941-509881368409
github.com/nicksnyder/go-i18n v1.10.1 // indirect
github.com/ns1/ns1-go v2.4.0+incompatible
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -963,6 +963,8 @@ github.com/mattn/go-shellwords v1.0.4/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vq
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mcuadros/go-lookup v0.0.0-20200831155250-80f87a4fa5ee h1:7Ac2RNGC8DAwDNd5uZyuYLoJOlVXyBGbO1VtFboDamk=
github.com/mcuadros/go-lookup v0.0.0-20200831155250-80f87a4fa5ee/go.mod h1:yd3I5pyIO5TrBH7+Ym94u8qp9xc6NTHAqESeI8kOJY8=
github.com/microsoft/azure-devops-go-api/azuredevops v1.0.0-b5 h1:YH424zrwLTlyHSH/GzLMJeu5zhYVZSx5RQxGKm1h96s=
github.com/microsoft/azure-devops-go-api/azuredevops v1.0.0-b5/go.mod h1:PoGiBqKSQK1vIfQ+yVaFcGjDySHvym6FM1cNYnwzbrY=
github.com/miekg/dns v1.0.8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
Expand Down
96 changes: 96 additions & 0 deletions providers/azuredevops/azuredevops_provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright 2021 The Terraformer Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package azuredevpos

import (
"errors"
"os"

"github.com/GoogleCloudPlatform/terraformer/terraformutils"
)

type AzureDevOpsProvider struct { //nolint
terraformutils.Provider
organizationURL string
personalAccessToken string
}

func (p *AzureDevOpsProvider) setEnvConfig() error {

organizationURL := os.Getenv("AZDO_ORG_SERVICE_URL")
if organizationURL == "" {
return errors.New("environment variable AZDO_ORG_SERVICE_URL missing")
}
personalAccessToken := os.Getenv("AZDO_PERSONAL_ACCESS_TOKEN")
if personalAccessToken == "" {
return errors.New("environment variable AZDO_PERSONAL_ACCESS_TOKEN missing")
}
p.organizationURL = organizationURL
p.personalAccessToken = personalAccessToken
return nil
}

func (p *AzureDevOpsProvider) Init(args []string) error {
err := p.setEnvConfig()
if err != nil {
return err
}
return nil
}

func (p *AzureDevOpsProvider) GetName() string {
return "azuredevops"
}

func (p *AzureDevOpsProvider) GetProviderData(arg ...string) map[string]interface{} {
return map[string]interface{}{}
}

func (p AzureDevOpsProvider) GetResourceConnections() map[string]map[string][]string {
supported := p.GetSupportedService()
connections := make(map[string]map[string][]string)
for serviceName, service := range supported {
if service2, ok := service.(AzureDevOpsServiceGenerator); ok {
if conn := service2.GetResourceConnections(); conn != nil {
connections[serviceName] = conn
}
}
}
return connections
}

func (p *AzureDevOpsProvider) GetSupportedService() map[string]terraformutils.ServiceGenerator {
return map[string]terraformutils.ServiceGenerator{
"project": &ProjectGenerator{},
"group": &GroupGenerator{},
"git_repository": &GitRepositoryGenerator{},
}
}

func (p *AzureDevOpsProvider) InitService(serviceName string, verbose bool) error {
var isSupported bool
if _, isSupported = p.GetSupportedService()[serviceName]; !isSupported {
return errors.New("azuredevpos: " + serviceName + " not supported service")
}
p.Service = p.GetSupportedService()[serviceName]
p.Service.SetName(serviceName)
p.Service.SetVerbose(verbose)
p.Service.SetProviderName(p.GetName())
p.Service.SetArgs(map[string]interface{}{
"organizationURL": p.organizationURL,
"personalAccessToken": p.personalAccessToken,
})
return nil
}
82 changes: 82 additions & 0 deletions providers/azuredevops/azuredevops_service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright 2021 The Terraformer Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package azuredevpos

import (
"context"
"log"

"github.com/microsoft/azure-devops-go-api/azuredevops"
"github.com/microsoft/azure-devops-go-api/azuredevops/core"
"github.com/microsoft/azure-devops-go-api/azuredevops/git"
"github.com/microsoft/azure-devops-go-api/azuredevops/graph"

"github.com/GoogleCloudPlatform/terraformer/terraformutils"
)

type AzureDevOpsServiceGenerator interface {
terraformutils.ServiceGenerator
GetResourceConnections() map[string][]string
}

type AzureDevOpsService struct { //nolint
terraformutils.Service
}

func (az *AzureDevOpsService) GetResourceConnections() map[string][]string {
return nil
}

func (az *AzureDevOpsService) getConnection() *azuredevops.Connection {

organizationURL := az.Args["organizationURL"].(string)
personalAccessToken := az.Args["personalAccessToken"].(string)
return azuredevops.NewPatConnection(organizationURL, personalAccessToken)
}

func (az *AzureDevOpsService) getCoreClient() (core.Client, error) {
ctx := context.Background()
client, err := core.NewClient(ctx, az.getConnection())
if err != nil {
log.Println(err)
return nil, err
}
return client, nil
}

func (az *AzureDevOpsService) getGraphClient() (graph.Client, error) {
ctx := context.Background()
client, err := graph.NewClient(ctx, az.getConnection())
if err != nil {
log.Println(err)
return nil, err
}
return client, nil
}

func (az *AzureDevOpsService) getGitClient() (git.Client, error) {
ctx := context.Background()
client, err := git.NewClient(ctx, az.getConnection())
if err != nil {
log.Println(err)
return nil, err
}
return client, nil
}

func (az *AzureDevOpsService) appendSimpleResource(id string, resourceName string, resourceType string) {
newResource := terraformutils.NewSimpleResource(id, resourceName, resourceType, az.ProviderName, []string{})
az.Resources = append(az.Resources, newResource)
}
50 changes: 50 additions & 0 deletions providers/azuredevops/git_repository.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package azuredevpos

import (
"context"

"github.com/microsoft/azure-devops-go-api/azuredevops/git"
)

type GitRepositoryGenerator struct {
AzureDevOpsService
}

func (az *GitRepositoryGenerator) listResources() ([]git.GitRepository, error) {

client, err := az.getGitClient()
if err != nil {
return nil, err
}
ctx := context.Background()
resources, err := client.GetRepositories(ctx, git.GetRepositoriesArgs{})
if err != nil {
return nil, err
}
return *resources, nil
}

func (az *GitRepositoryGenerator) appendResource(resource *git.GitRepository) {

id := *resource.Id
az.appendSimpleResource(id.String(), *resource.Name, "azuredevops_git_repository")
}

func (az *GitRepositoryGenerator) InitResources() error {

resources, err := az.listResources()
if err != nil {
return err
}
for _, resource := range resources {
az.appendResource(&resource)
}
return nil
}

func (az *GitRepositoryGenerator) GetResourceConnections() map[string][]string {

return map[string][]string{
"project": {"project_id", "id"},
}
}
58 changes: 58 additions & 0 deletions providers/azuredevops/group.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package azuredevpos

import (
"context"

"github.com/microsoft/azure-devops-go-api/azuredevops/graph"
)

type GroupGenerator struct {
AzureDevOpsService
}

func (az *GroupGenerator) listResources() ([]graph.GraphGroup, error) {

client, fail := az.getGraphClient()
if fail != nil {
return nil, fail
}
ctx := context.Background()
var resources []graph.GraphGroup
pageArgs := graph.ListGroupsArgs{}
pages, err := client.ListGroups(ctx, pageArgs)
for ; err == nil; pages, err = client.ListGroups(ctx, pageArgs) {
resources = append(resources, *pages.GraphGroups...)
if pages.ContinuationToken == nil {
return resources, nil
}
pageArgs = graph.ListGroupsArgs{
ContinuationToken: &(*pages.ContinuationToken)[0],
}
}
return nil, err
}

func (az *GroupGenerator) appendResource(resource *graph.GraphGroup) {

resourceName := firstNonEmpty(resource.DisplayName, resource.MailAddress, resource.OriginId)
az.appendSimpleResource(*resource.Descriptor, *resourceName, "azuredevops_group")
}

func (az *GroupGenerator) InitResources() error {

resources, err := az.listResources()
if err != nil {
return err
}
for _, resource := range resources {
az.appendResource(&resource)
}
return nil
}

func (az *GroupGenerator) GetResourceConnections() map[string][]string {

return map[string][]string{
"project": {"scope", "id"},
}
}
Loading

0 comments on commit 4f25129

Please sign in to comment.