-
Notifications
You must be signed in to change notification settings - Fork 126
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding Azure DevOps as a provider (#217)
* feat: #94 adding skeleton code for ado provider * feat: adding tests * feat: adding tests * feat: adding tests * rerun code gen * adding another test * removing unneeded and confusing url parsing * updating a bad comment
- Loading branch information
Showing
13 changed files
with
335 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package git | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"github.com/microsoft/azure-devops-go-api/azuredevops" | ||
ado "github.com/microsoft/azure-devops-go-api/azuredevops/git" | ||
"net/url" | ||
"strings" | ||
"time" | ||
) | ||
|
||
//go:generate mockery --name Ado* --output ado/mocks --case snake | ||
type ( | ||
AdoClient interface { | ||
CreateRepository(context.Context, ado.CreateRepositoryArgs) (*ado.GitRepository, error) | ||
} | ||
|
||
AdoUrl interface { | ||
GetProjectName() string | ||
} | ||
|
||
adoGit struct { | ||
adoClient AdoClient | ||
opts *ProviderOptions | ||
adoUrl AdoUrl | ||
} | ||
|
||
adoGitUrl struct { | ||
loginUrl string | ||
subscription string | ||
projectName string | ||
} | ||
) | ||
|
||
const Azure = "azure" | ||
const AzureHostName = "dev.azure" | ||
const timeoutTime = 10 * time.Second | ||
|
||
func newAdo(opts *ProviderOptions) (Provider, error) { | ||
adoUrl, err := parseAdoUrl(opts.Host) | ||
if err != nil { | ||
return nil, err | ||
} | ||
connection := azuredevops.NewPatConnection(adoUrl.loginUrl, opts.Auth.Password) | ||
ctx, cancel := context.WithTimeout(context.Background(), timeoutTime) | ||
defer cancel() | ||
// FYI: ado also has a "core" client that can be used to update project, teams, and other ADO constructs | ||
gitClient, err := ado.NewClient(ctx, connection) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &adoGit{ | ||
adoClient: gitClient, | ||
opts: opts, | ||
adoUrl: adoUrl, | ||
}, nil | ||
} | ||
|
||
func (g *adoGit) CreateRepository(ctx context.Context, opts *CreateRepoOptions) (string, error) { | ||
if opts.Name == "" { | ||
return "", fmt.Errorf("name needs to be provided to create an azure devops repository. "+ | ||
"name: '%s'", opts.Name) | ||
} | ||
gitRepoToCreate := &ado.GitRepositoryCreateOptions{ | ||
Name: &opts.Name, | ||
} | ||
project := g.adoUrl.GetProjectName() | ||
createRepositoryArgs := ado.CreateRepositoryArgs{ | ||
GitRepositoryToCreate: gitRepoToCreate, | ||
Project: &project, | ||
} | ||
repository, err := g.adoClient.CreateRepository(ctx, createRepositoryArgs) | ||
if err != nil { | ||
return "", err | ||
} | ||
return *repository.RemoteUrl, nil | ||
} | ||
|
||
func (a *adoGitUrl) GetProjectName() string { | ||
return a.projectName | ||
} | ||
|
||
// getLoginUrl parses a URL to retrieve the subscription and project name | ||
func parseAdoUrl(host string) (*adoGitUrl, error) { | ||
u, err := url.Parse(host) | ||
if err != nil { | ||
return nil, err | ||
} | ||
var sub, project string | ||
path := strings.Split(u.Path, "/") | ||
if len(path) < 5 { | ||
return nil, fmt.Errorf("unable to parse Azure DevOps url") | ||
} else { | ||
// 1 since the path starts with a slash | ||
sub = path[1] | ||
project = path[2] | ||
} | ||
loginUrl := fmt.Sprintf("%s://%s/%s", u.Scheme, u.Host, sub) | ||
return &adoGitUrl{ | ||
loginUrl: loginUrl, | ||
subscription: sub, | ||
projectName: project, | ||
}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
package git | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
adoMock "github.com/argoproj-labs/argocd-autopilot/pkg/git/ado/mocks" | ||
ado "github.com/microsoft/azure-devops-go-api/azuredevops/git" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/mock" | ||
"testing" | ||
) | ||
|
||
func Test_adoGit_CreateRepository(t *testing.T) { | ||
remoteURL := "https://dev.azure.com/SUB/PROJECT/_git/REPO" | ||
emptyFunc := func(client *adoMock.AdoClient, url *adoMock.AdoUrl) {} | ||
type args struct { | ||
ctx context.Context | ||
opts *CreateRepoOptions | ||
} | ||
tests := []struct { | ||
name string | ||
mockClient func(client *adoMock.AdoClient, url *adoMock.AdoUrl) | ||
args args | ||
want string | ||
wantErr assert.ErrorAssertionFunc | ||
}{ | ||
{name: "Empty Name", mockClient: emptyFunc, args: args{ | ||
ctx: context.TODO(), | ||
opts: &CreateRepoOptions{ | ||
Owner: "rumstead", | ||
Name: "", | ||
}, | ||
}, want: "", wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { | ||
return true | ||
}}, | ||
{name: "Failure creating repo", mockClient: func(client *adoMock.AdoClient, url *adoMock.AdoUrl) { | ||
client.On("CreateRepository", context.TODO(), | ||
mock.AnythingOfType("CreateRepositoryArgs")). | ||
Return(nil, errors.New("ah an error")) | ||
url.On("GetProjectName").Return("blah") | ||
}, args: args{ | ||
ctx: context.TODO(), | ||
opts: &CreateRepoOptions{ | ||
Owner: "rumstead", | ||
Name: "name", | ||
}, | ||
}, want: "", wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { | ||
return true | ||
}}, | ||
{name: "Success creating repo", mockClient: func(client *adoMock.AdoClient, url *adoMock.AdoUrl) { | ||
url.On("GetProjectName").Return("PROJECT") | ||
client.On("CreateRepository", context.TODO(), mock.AnythingOfType("CreateRepositoryArgs")).Return(&ado.GitRepository{ | ||
Links: nil, | ||
DefaultBranch: nil, | ||
Id: nil, | ||
IsFork: nil, | ||
Name: nil, | ||
ParentRepository: nil, | ||
Project: nil, | ||
RemoteUrl: &remoteURL, | ||
Size: nil, | ||
SshUrl: nil, | ||
Url: nil, | ||
ValidRemoteUrls: nil, | ||
WebUrl: nil, | ||
}, nil) | ||
}, args: args{ | ||
ctx: context.TODO(), | ||
opts: &CreateRepoOptions{ | ||
Owner: "rumstead", | ||
Name: "name", | ||
}, | ||
}, want: "https://dev.azure.com/SUB/PROJECT/_git/REPO", wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { | ||
return false | ||
}}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
mockClient := &adoMock.AdoClient{} | ||
mockUrl := &adoMock.AdoUrl{} | ||
tt.mockClient(mockClient, mockUrl) | ||
g := &adoGit{ | ||
adoClient: mockClient, | ||
adoUrl: mockUrl, | ||
} | ||
got, err := g.CreateRepository(tt.args.ctx, tt.args.opts) | ||
if !tt.wantErr(t, err, fmt.Sprintf("CreateRepository(%v, %v)", tt.args.ctx, tt.args.opts)) { | ||
return | ||
} | ||
assert.Equalf(t, tt.want, got, "CreateRepository(%v, %v)", tt.args.ctx, tt.args.opts) | ||
}) | ||
} | ||
} | ||
|
||
func Test_parseAdoUrl(t *testing.T) { | ||
type args struct { | ||
host string | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
want *adoGitUrl | ||
wantErr assert.ErrorAssertionFunc | ||
}{ | ||
{name: "Invalid URL", args: args{host: "https://dev.azure.com"}, want: nil, wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { | ||
return true | ||
}}, | ||
// url taking from the url_test in the url/net module | ||
{name: "Parse Error", args: args{host: "http://[fe80::%31]:8080/"}, want: nil, wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { | ||
return true | ||
}}, | ||
{name: "Parse URL", args: args{host: "https://dev.azure.com/SUB/PROJECT/_git/REPO "}, want: &adoGitUrl{ | ||
loginUrl: "https://dev.azure.com/SUB", | ||
subscription: "SUB", | ||
projectName: "PROJECT", | ||
}, wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { | ||
return false | ||
}}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
got, err := parseAdoUrl(tt.args.host) | ||
if !tt.wantErr(t, err, fmt.Sprintf("parseAdoUrl(%v)", tt.args.host)) { | ||
return | ||
} | ||
assert.Equalf(t, tt.want, got, "parseAdoUrl(%v)", tt.args.host) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.