Skip to content

Commit dc21865

Browse files
authored
Merge pull request #1328 from xuzhang3/f/extension
[New Resource] `azuredevops_extension`
2 parents baea9cf + f019446 commit dc21865

File tree

11 files changed

+6593
-0
lines changed

11 files changed

+6593
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
package acceptancetests
2+
3+
import (
4+
"fmt"
5+
"regexp"
6+
"strings"
7+
"testing"
8+
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
10+
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
11+
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/extensionmanagement"
12+
"github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/acceptancetests/testutils"
13+
"github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/client"
14+
)
15+
16+
func TestAccExtension_basic(t *testing.T) {
17+
publisherId := "ms-securitydevops"
18+
extensionId := "microsoft-security-devops-azdevops"
19+
tfNode := "azuredevops_extension.test"
20+
resource.Test(t, resource.TestCase{
21+
PreCheck: func() { testutils.PreCheck(t, nil) },
22+
ProviderFactories: testutils.GetProviderFactories(),
23+
CheckDestroy: checkExtensionDestroyed,
24+
Steps: []resource.TestStep{
25+
{
26+
Config: hclExtensionBasic(publisherId, extensionId),
27+
Check: resource.ComposeTestCheckFunc(
28+
checkExtensionExist(extensionId),
29+
resource.TestCheckResourceAttrSet(tfNode, "extension_id"),
30+
resource.TestCheckResourceAttrSet(tfNode, "publisher_id"),
31+
resource.TestCheckResourceAttrSet(tfNode, "publisher_name"),
32+
resource.TestCheckResourceAttrSet(tfNode, "extension_name"),
33+
),
34+
},
35+
{
36+
ResourceName: tfNode,
37+
ImportState: true,
38+
ImportStateId: fmt.Sprintf("%s/%s", publisherId, extensionId),
39+
ImportStateVerify: true,
40+
},
41+
},
42+
})
43+
}
44+
45+
func TestAccExtension_complete(t *testing.T) {
46+
publisherId := "ms-securitydevops"
47+
extensionId := "microsoft-security-devops-azdevops"
48+
tfNode := "azuredevops_extension.test"
49+
resource.Test(t, resource.TestCase{
50+
PreCheck: func() { testutils.PreCheck(t, nil) },
51+
ProviderFactories: testutils.GetProviderFactories(),
52+
CheckDestroy: checkExtensionDestroyed,
53+
Steps: []resource.TestStep{
54+
{
55+
Config: hclExtensionComplete(publisherId, extensionId),
56+
Check: resource.ComposeTestCheckFunc(
57+
checkExtensionExist(extensionId),
58+
resource.TestCheckResourceAttrSet(tfNode, "extension_id"),
59+
resource.TestCheckResourceAttrSet(tfNode, "publisher_id"),
60+
resource.TestCheckResourceAttrSet(tfNode, "publisher_name"),
61+
resource.TestCheckResourceAttrSet(tfNode, "extension_name"),
62+
resource.TestCheckResourceAttrSet(tfNode, "scope.#"),
63+
resource.TestCheckResourceAttrSet(tfNode, "version"),
64+
resource.TestCheckResourceAttrSet(tfNode, "disabled"),
65+
),
66+
},
67+
{
68+
ResourceName: tfNode,
69+
ImportState: true,
70+
ImportStateId: fmt.Sprintf("%s/%s", publisherId, extensionId),
71+
ImportStateVerify: true,
72+
},
73+
},
74+
})
75+
}
76+
77+
func TestAccExtension_update(t *testing.T) {
78+
publisherId := "ms-securitydevops"
79+
extensionId := "microsoft-security-devops-azdevops"
80+
tfNode := "azuredevops_extension.test"
81+
resource.Test(t, resource.TestCase{
82+
PreCheck: func() { testutils.PreCheck(t, nil) },
83+
ProviderFactories: testutils.GetProviderFactories(),
84+
CheckDestroy: checkExtensionDestroyed,
85+
Steps: []resource.TestStep{
86+
{
87+
Config: hclExtensionBasic(publisherId, extensionId),
88+
Check: resource.ComposeTestCheckFunc(
89+
checkExtensionExist(extensionId),
90+
resource.TestCheckResourceAttrSet(tfNode, "extension_id"),
91+
resource.TestCheckResourceAttrSet(tfNode, "publisher_id"),
92+
resource.TestCheckResourceAttrSet(tfNode, "publisher_name"),
93+
resource.TestCheckResourceAttrSet(tfNode, "extension_name"),
94+
resource.TestCheckResourceAttrSet(tfNode, "scope.#"),
95+
resource.TestCheckResourceAttrSet(tfNode, "version"),
96+
resource.TestCheckResourceAttrSet(tfNode, "disabled"),
97+
),
98+
},
99+
{
100+
ResourceName: tfNode,
101+
ImportState: true,
102+
ImportStateId: fmt.Sprintf("%s/%s", publisherId, extensionId),
103+
ImportStateVerify: true,
104+
},
105+
{
106+
Config: hclExtensionUpdate(publisherId, extensionId, true),
107+
Check: resource.ComposeTestCheckFunc(
108+
checkExtensionExist(extensionId),
109+
resource.TestCheckResourceAttrSet(tfNode, "extension_id"),
110+
resource.TestCheckResourceAttrSet(tfNode, "publisher_id"),
111+
resource.TestCheckResourceAttrSet(tfNode, "publisher_name"),
112+
resource.TestCheckResourceAttrSet(tfNode, "extension_name"),
113+
resource.TestCheckResourceAttr(tfNode, "disabled", "true"),
114+
),
115+
},
116+
{
117+
ResourceName: tfNode,
118+
ImportState: true,
119+
ImportStateId: fmt.Sprintf("%s/%s", publisherId, extensionId),
120+
ImportStateVerify: true,
121+
},
122+
{
123+
Config: hclExtensionUpdate(publisherId, extensionId, false),
124+
Check: resource.ComposeTestCheckFunc(
125+
checkExtensionExist(extensionId),
126+
resource.TestCheckResourceAttrSet(tfNode, "extension_id"),
127+
resource.TestCheckResourceAttrSet(tfNode, "publisher_id"),
128+
resource.TestCheckResourceAttrSet(tfNode, "publisher_name"),
129+
resource.TestCheckResourceAttrSet(tfNode, "extension_name"),
130+
resource.TestCheckResourceAttrSet(tfNode, "scope.#"),
131+
resource.TestCheckResourceAttr(tfNode, "disabled", "false"),
132+
),
133+
},
134+
{
135+
ResourceName: tfNode,
136+
ImportState: true,
137+
ImportStateId: fmt.Sprintf("%s/%s", publisherId, extensionId),
138+
ImportStateVerify: true,
139+
},
140+
},
141+
})
142+
}
143+
144+
func TestAccExtension_requireImportError(t *testing.T) {
145+
publisherId := "ms-securitydevops"
146+
extensionId := "microsoft-security-devops-azdevops"
147+
tfNode := "azuredevops_extension.test"
148+
resource.Test(t, resource.TestCase{
149+
PreCheck: func() { testutils.PreCheck(t, nil) },
150+
ProviderFactories: testutils.GetProviderFactories(),
151+
CheckDestroy: checkExtensionDestroyed,
152+
Steps: []resource.TestStep{
153+
{
154+
Config: hclExtensionBasic(publisherId, extensionId),
155+
Check: resource.ComposeTestCheckFunc(
156+
checkExtensionExist(extensionId),
157+
resource.TestCheckResourceAttrSet(tfNode, "extension_id"),
158+
resource.TestCheckResourceAttrSet(tfNode, "publisher_id"),
159+
resource.TestCheckResourceAttrSet(tfNode, "publisher_name"),
160+
resource.TestCheckResourceAttrSet(tfNode, "extension_name"),
161+
resource.TestCheckResourceAttrSet(tfNode, "scope.#"),
162+
resource.TestCheckResourceAttrSet(tfNode, "version"),
163+
resource.TestCheckResourceAttrSet(tfNode, "disabled"),
164+
),
165+
},
166+
{
167+
ResourceName: tfNode,
168+
ImportState: true,
169+
ImportStateId: fmt.Sprintf("%s/%s", publisherId, extensionId),
170+
ImportStateVerify: true,
171+
},
172+
{
173+
Config: hclExtensionImportError(publisherId, extensionId),
174+
ExpectError: requiresExtensionImportError(publisherId, extensionId),
175+
},
176+
},
177+
})
178+
}
179+
180+
func checkExtensionDestroyed(s *terraform.State) error {
181+
clients := testutils.GetProvider().Meta().(*client.AggregatedClient)
182+
for _, res := range s.RootModule().Resources {
183+
if res.Type != "azuredevops_extension" {
184+
continue
185+
}
186+
ids := strings.Split(res.Primary.ID, "/")
187+
188+
_, err := clients.ExtensionManagementClient.GetInstalledExtensionByName(clients.Ctx, extensionmanagement.GetInstalledExtensionByNameArgs{
189+
PublisherName: &ids[0],
190+
ExtensionName: &ids[1],
191+
})
192+
193+
if err == nil {
194+
return fmt.Errorf(" Extension with Publisher ID=%s , Extension ID: %s should not exist", ids[0], ids[1])
195+
}
196+
}
197+
return nil
198+
}
199+
200+
func checkExtensionExist(expectedExtensionId string) resource.TestCheckFunc {
201+
return func(s *terraform.State) error {
202+
res, ok := s.RootModule().Resources["azuredevops_extension.test"]
203+
if !ok {
204+
return fmt.Errorf(" Did not find `azuredevops_extension` in the TF state")
205+
}
206+
207+
clients := testutils.GetProvider().Meta().(*client.AggregatedClient)
208+
ids := strings.Split(res.Primary.ID, "/")
209+
210+
extension, err := clients.ExtensionManagementClient.GetInstalledExtensionByName(clients.Ctx, extensionmanagement.GetInstalledExtensionByNameArgs{
211+
PublisherName: &ids[0],
212+
ExtensionName: &ids[1],
213+
})
214+
215+
if err != nil {
216+
return fmt.Errorf(" Extension with Publisher ID=%s , Extension ID: %s cannot be found!. Error=%v", ids[0], ids[1], err)
217+
}
218+
219+
if *extension.ExtensionId != expectedExtensionId {
220+
return fmt.Errorf(" Extension with Publisher ID=%s has Extension ID=%s, but expected Extension ID=%s", *extension.PublisherId, *extension.ExtensionId, expectedExtensionId)
221+
}
222+
return nil
223+
}
224+
}
225+
226+
func requiresExtensionImportError(publisherId, extensionId string) *regexp.Regexp {
227+
message := "Installing extension for Publisher: %s, Name: %s. Error: TF1590010: Extension %s.%s is already installed in this organization"
228+
return regexp.MustCompile(fmt.Sprintf(message, publisherId, extensionId, publisherId, extensionId))
229+
}
230+
231+
func hclExtensionBasic(publisherId, extensionId string) string {
232+
return fmt.Sprintf(`
233+
resource "azuredevops_extension" "test" {
234+
publisher_id = "%s"
235+
extension_id = "%s"
236+
}`, publisherId, extensionId)
237+
}
238+
239+
func hclExtensionComplete(publisherId, extensionId string) string {
240+
return fmt.Sprintf(`
241+
resource "azuredevops_extension" "test" {
242+
publisher_id = "%s"
243+
extension_id = "%s"
244+
disabled = false
245+
}`, publisherId, extensionId)
246+
}
247+
248+
func hclExtensionUpdate(publisherId, extensionId string, disabled bool) string {
249+
return fmt.Sprintf(`
250+
resource "azuredevops_extension" "test" {
251+
publisher_id = "%s"
252+
extension_id = "%s"
253+
disabled = %t
254+
}`, publisherId, extensionId, disabled)
255+
}
256+
257+
func hclExtensionImportError(publisherId, extensionId string) string {
258+
return fmt.Sprintf(`
259+
%s
260+
261+
resource "azuredevops_extension" "import" {
262+
publisher_id = azuredevops_extension.test.publisher_id
263+
extension_id = azuredevops_extension.test.extension_id
264+
}`, hclExtensionBasic(publisherId, extensionId))
265+
}

azuredevops/internal/client/client.go

+9
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/core"
1313
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/dashboard"
1414
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/elastic"
15+
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/extensionmanagement"
1516
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/featuremanagement"
1617
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/feed"
1718
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/git"
@@ -61,6 +62,7 @@ type AggregatedClient struct {
6162
PipelinesChecksClientExtras pipelineschecksextras.Client
6263
PolicyClient policy.Client
6364
ElasticClient elastic.Client
65+
ExtensionManagementClient extensionmanagement.Client
6466
ReleaseClient release.Client
6567
ServiceEndpointClient serviceendpoint.Client
6668
TaskAgentClient taskagent.Client
@@ -108,6 +110,12 @@ func GetAzdoClient(azdoTokenProvider func() (string, error), organizationURL str
108110

109111
elasticClient := elastic.NewClient(ctx, connection)
110112

113+
extensionManagementClient, err := extensionmanagement.NewClient(ctx, connection)
114+
if err != nil {
115+
log.Printf("getAzdoClient(): extensionmanagement.NewClient failed.")
116+
return nil, err
117+
}
118+
111119
dashboardClient, err := dashboard.NewClient(ctx, connection)
112120
if err != nil {
113121
log.Printf("getAzdoClient(): dashboardClient.NewClient failed.")
@@ -220,6 +228,7 @@ func GetAzdoClient(azdoTokenProvider func() (string, error), organizationURL str
220228
DashboardClient: dashboardClient,
221229
DashboardClientExtra: dashboardClientExtra,
222230
ElasticClient: elasticClient,
231+
ExtensionManagementClient: extensionManagementClient,
223232
GitReposClient: gitReposClient,
224233
GraphClient: graphClient,
225234
OperationsClient: operationsClient,

0 commit comments

Comments
 (0)