-
Notifications
You must be signed in to change notification settings - Fork 36
Azure devops #216
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Azure devops #216
Changes from all commits
3a60af6
7c0a256
08d5cc2
dacd214
8de69f7
addba5d
a281f36
7ebb4c2
4e797ed
94bd68a
b9a6ffd
3d7c068
8f846ba
25257c1
a7ffc7c
748da57
44c99a9
73d2cad
da06ba2
218aa50
f1be3fe
65d258b
b09453d
aaad427
e80740b
f957e4a
577f59e
97c8e28
2d93283
e3b554c
8a3c83a
ab2b039
8aad5eb
df7e404
545900b
e2c87e1
2b7d1b1
a9c2c92
97994a4
0ee0913
c5b9980
975a68c
2e3b111
2256f08
f3bf893
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
name: e2e-azuredevops | ||
|
||
on: | ||
workflow_dispatch: | ||
push: | ||
branches: ["*"] | ||
tags-ignore: ["*"] | ||
|
||
jobs: | ||
test: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Checkout | ||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 | ||
- name: Restore Go cache | ||
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1 | ||
with: | ||
path: ~/go/pkg/mod | ||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} | ||
restore-keys: | | ||
${{ runner.os }}-go- | ||
- name: Setup Go | ||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0 | ||
with: | ||
go-version: 1.18.x | ||
- name: Run tests | ||
run: | | ||
[ -n "${{ secrets.AZURE_TOKEN }}" ] && export AZURE_TOKEN=${{ secrets.AZURE_TOKEN }} || echo "using default AZURE_TOKEN" | ||
[ -n "${{ secrets.AZURE_DOMAIN }}" ] && export AZURE_DOMAIN=${{ secrets.AZURE_DOMAIN }} || echo "using default AZURE_DOMAIN" | ||
make test-e2e-azuredevops | ||
- name: Upload coverage to Codecov | ||
uses: codecov/codecov-action@894ff025c7b54547a9a2a1e9f228beae737ad3c2 # v3.1.3 | ||
with: | ||
files: ./coverage.txt |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,71 @@ | ||||||
/* | ||||||
Copyright 2023 The Flux CD contributors. | ||||||
|
||||||
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 azuredevops | ||||||
|
||||||
import ( | ||||||
"context" | ||||||
"fmt" | ||||||
|
||||||
"github.com/fluxcd/go-git-providers/gitprovider" | ||||||
"github.com/microsoft/azure-devops-go-api/azuredevops/v6" | ||||||
"github.com/microsoft/azure-devops-go-api/azuredevops/v6/core" | ||||||
"github.com/microsoft/azure-devops-go-api/azuredevops/v6/git" | ||||||
|
||||||
"net/url" | ||||||
) | ||||||
|
||||||
// NewClient creates a new Client instance for Azure Devops API endpoints. | ||||||
// The client accepts a personal token used which is used to authenticate and a context as an argument, | ||||||
// Variadic parameters gitprovider.ClientOption are used to pass additional options to the gitprovider.Client. | ||||||
func NewClient(personalAccessToken string, ctx context.Context, optFns ...gitprovider.ClientOption) (gitprovider.Client, error) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this func in a file named |
||||||
opts, err := gitprovider.MakeClientOptions(optFns...) | ||||||
if err != nil { | ||||||
return nil, err | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should entertain the habit of wrapping errors and add context:
Suggested change
|
||||||
} | ||||||
|
||||||
if opts.Domain == nil { | ||||||
return nil, fmt.Errorf("domain is required") | ||||||
} | ||||||
|
||||||
domain := *opts.Domain | ||||||
u, err := url.Parse(domain) | ||||||
if err != nil { | ||||||
return nil, fmt.Errorf("invalid domain: %w", err) | ||||||
} | ||||||
if u.Scheme == "" || u.Scheme == "http" { | ||||||
domain = fmt.Sprintf("https://%s%s", u.Host, u.Path) | ||||||
} | ||||||
|
||||||
connection := azuredevops.NewPatConnection(domain, personalAccessToken) | ||||||
// coreClient provides access to Azure Devops organization,projects and teams | ||||||
coreClient, err := core.NewClient(ctx, connection) | ||||||
if err != nil { | ||||||
return nil, err | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
} | ||||||
// gitClient provides access to the Azure Devops Git repositories the files,trees,commits and refs | ||||||
gitClient, err := git.NewClient(ctx, connection) | ||||||
if err != nil { | ||||||
return nil, err | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
} | ||||||
|
||||||
destructiveActions := false | ||||||
if opts.EnableDestructiveAPICalls != nil { | ||||||
destructiveActions = *opts.EnableDestructiveAPICalls | ||||||
} | ||||||
|
||||||
return newClient(coreClient, gitClient, domain, destructiveActions), nil | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
/* | ||
Copyright 2023 The Flux CD contributors. | ||
|
||
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 azuredevops | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
|
||
"github.com/fluxcd/go-git-providers/gitprovider" | ||
) | ||
|
||
func Test_DomainVariations(t *testing.T) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about other opts we want to test? |
||
tests := []struct { | ||
name string | ||
opts gitprovider.ClientOption | ||
want string | ||
expectedErrs []error | ||
}{ | ||
{ | ||
name: "custom domain without protocol", | ||
opts: gitprovider.WithDomain("dev.azure.com/test-demo"), | ||
want: "https://dev.azure.com/test-demo", | ||
}, | ||
{ | ||
name: "custom domain with https protocol", | ||
opts: gitprovider.WithDomain("https://dev.azure.com/test-demo"), | ||
want: "https://dev.azure.com/test-demo", | ||
}, | ||
{ | ||
name: "custom domain with http protocol", | ||
opts: gitprovider.WithDomain("http://dev.azure.com/test-demo"), | ||
want: "https://dev.azure.com/test-demo", | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
ctx := context.Background() | ||
c2, err := NewClient("personalAccessToken", ctx, tt.opts) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if diff := cmp.Diff(tt.want, c2.SupportedDomain()); diff != "" { | ||
t.Errorf("New Azure client returned domain (want -> got): %s", diff) | ||
} | ||
}) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
/* | ||
Copyright 2023 The Flux CD contributors. | ||
|
||
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 azuredevops | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/url" | ||
|
||
"github.com/fluxcd/go-git-providers/gitprovider" | ||
"github.com/microsoft/azure-devops-go-api/azuredevops/v6/core" | ||
"github.com/microsoft/azure-devops-go-api/azuredevops/v6/git" | ||
) | ||
|
||
// ProviderID is the provider ID for AzureDevops. | ||
const ProviderID = gitprovider.ProviderID("azureDevops") | ||
|
||
func newClient(c core.Client, g git.Client, domain string, destructiveActions bool) *ProviderClient { | ||
ctx := &clientContext{ | ||
c, | ||
g, | ||
domain, | ||
destructiveActions} | ||
return &ProviderClient{ | ||
clientContext: ctx, | ||
orgs: &OrganizationsClient{ | ||
clientContext: ctx, | ||
}, | ||
repos: &RepositoriesClient{ | ||
clientContext: ctx, | ||
}, | ||
} | ||
} | ||
|
||
// ProviderClient implements the gitprovider.Client interface. | ||
var _ gitprovider.Client = &ProviderClient{} | ||
|
||
type clientContext struct { | ||
c core.Client | ||
g git.Client | ||
domain string | ||
destructiveActions bool | ||
} | ||
|
||
// ProviderClient is the AzureDevops implementation of the gitprovider.Client interface. | ||
type ProviderClient struct { | ||
*clientContext | ||
|
||
orgs *OrganizationsClient | ||
repos *RepositoriesClient | ||
} | ||
|
||
// Raw returns the underlying AzureDevops client. | ||
// It returns the core.Client | ||
func (c *ProviderClient) Raw() interface{} { | ||
return c.c | ||
} | ||
|
||
// UserRepositories returns the UserRepositoriesClient for the client. | ||
func (c *ProviderClient) UserRepositories() gitprovider.UserRepositoriesClient { | ||
// Method not support for AzureDevops | ||
return nil | ||
} | ||
|
||
// OrgRepositories returns the OrgRepositoriesClient for the client. | ||
func (c *ProviderClient) OrgRepositories() gitprovider.OrgRepositoriesClient { | ||
return c.repos | ||
} | ||
|
||
// Organizations returns the OrganizationsClient for the client. | ||
func (c *ProviderClient) Organizations() gitprovider.OrganizationsClient { | ||
return c.orgs | ||
} | ||
|
||
// SupportedDomain returns the domain endpoint for this client, e.g. "dev.azure.com" or | ||
// "my-custom-git-server.com:6443". | ||
// This field is set at client creation time, and can't be changed. | ||
func (c *ProviderClient) SupportedDomain() string { | ||
u, _ := url.Parse(c.domain) | ||
if u.Scheme == "" { | ||
c.domain = fmt.Sprintf("https://%s", c.domain) | ||
} | ||
return c.domain | ||
} | ||
|
||
// ProviderID returns the provider ID for this client, e.g. "azuredevops". | ||
func (c *ProviderClient) ProviderID() gitprovider.ProviderID { | ||
return ProviderID | ||
} | ||
|
||
// HasTokenPermission returns whether the token has the given permission. | ||
func (c *ProviderClient) HasTokenPermission(ctx context.Context, permission gitprovider.TokenPermission) (bool, error) { | ||
return false, gitprovider.ErrNoProviderSupport | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,90 @@ | ||||||
/* | ||||||
Copyright 2023 The Flux CD contributors. | ||||||
|
||||||
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 azuredevops | ||||||
|
||||||
import ( | ||||||
"context" | ||||||
|
||||||
"github.com/fluxcd/go-git-providers/gitprovider" | ||||||
"github.com/microsoft/azure-devops-go-api/azuredevops/v6/core" | ||||||
) | ||||||
|
||||||
// OrganizationsClient implements the gitprovider.OrganizationsClient interface. | ||||||
var _ gitprovider.OrganizationsClient = &OrganizationsClient{} | ||||||
|
||||||
// OrganizationsClient operates on the projects a user has access to. | ||||||
type OrganizationsClient struct { | ||||||
*clientContext | ||||||
} | ||||||
|
||||||
// Get returns the specific project the user has access to. | ||||||
func (c *OrganizationsClient) Get(ctx context.Context, ref gitprovider.OrganizationRef) (gitprovider.Organization, error) { | ||||||
args := core.GetProjectArgs{ProjectId: &ref.Organization} | ||||||
// https://pkg.go.dev/github.com/microsoft/azure-devops-go-api/azuredevops/[email protected]/core#ClientImpl.GetProject | ||||||
project, err := c.c.GetProject(ctx, args) | ||||||
if err != nil { | ||||||
return nil, err | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
} | ||||||
|
||||||
ref.SetKey(project.Id.String()) | ||||||
return newProject(c.clientContext, *project, ref), nil | ||||||
} | ||||||
|
||||||
// List returns all available projects, using multiple requests if needed. | ||||||
func (c *OrganizationsClient) List(ctx context.Context) ([]gitprovider.Organization, error) { | ||||||
args := core.GetProjectsArgs{} | ||||||
projects := make([]gitprovider.Organization, 0) | ||||||
|
||||||
return c.list(ctx, args, projects) | ||||||
} | ||||||
|
||||||
func (c *OrganizationsClient) list(ctx context.Context, args core.GetProjectsArgs, projects []gitprovider.Organization) ([]gitprovider.Organization, error) { | ||||||
// https://pkg.go.dev/github.com/microsoft/azure-devops-go-api/azuredevops/[email protected]/core#ClientImpl.GetProjects | ||||||
apiObjs, err := c.c.GetProjects(ctx, args) | ||||||
if err != nil { | ||||||
return nil, err | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
} | ||||||
|
||||||
for _, apiObj := range apiObjs.Value { | ||||||
ref := gitprovider.OrganizationRef{ | ||||||
Domain: *apiObj.Url, | ||||||
Organization: *apiObj.Name, | ||||||
} | ||||||
|
||||||
teamProject := core.TeamProject{ | ||||||
Id: apiObj.Id, | ||||||
Name: apiObj.Name, | ||||||
} | ||||||
|
||||||
ref.SetKey(apiObj.Id.String()) | ||||||
projects = append(projects, newProject(c.clientContext, teamProject, ref)) | ||||||
} | ||||||
|
||||||
if apiObjs.ContinuationToken != "" { | ||||||
args := core.GetProjectsArgs{ | ||||||
ContinuationToken: &apiObjs.ContinuationToken, | ||||||
} | ||||||
return c.list(ctx, args, projects) | ||||||
} | ||||||
|
||||||
return projects, nil | ||||||
} | ||||||
|
||||||
// Children is not supported by AzureDevops. | ||||||
func (c *OrganizationsClient) Children(_ context.Context, _ gitprovider.OrganizationRef) ([]gitprovider.Organization, error) { | ||||||
return nil, gitprovider.ErrNoProviderSupport | ||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.