Skip to content

Commit 0464191

Browse files
authored
Add singular data source for retrieving a Maven artifact from an Artifact Registry repository (#14782)
1 parent 1a52c1d commit 0464191

File tree

4 files changed

+418
-0
lines changed

4 files changed

+418
-0
lines changed

mmv1/third_party/terraform/provider/provider_mmv1_resources.go.tmpl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ var handwrittenDatasources = map[string]*schema.Resource{
2929
"google_artifact_registry_docker_image": artifactregistry.DataSourceArtifactRegistryDockerImage(),
3030
"google_artifact_registry_docker_images": artifactregistry.DataSourceArtifactRegistryDockerImages(),
3131
"google_artifact_registry_locations": artifactregistry.DataSourceGoogleArtifactRegistryLocations(),
32+
"google_artifact_registry_maven_artifact": artifactregistry.DataSourceArtifactRegistryMavenArtifact(),
3233
"google_artifact_registry_npm_package": artifactregistry.DataSourceArtifactRegistryNpmPackage(),
3334
"google_artifact_registry_package": artifactregistry.DataSourceArtifactRegistryPackage(),
3435
"google_artifact_registry_python_package": artifactregistry.DataSourceArtifactRegistryPythonPackage(),
Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
package artifactregistry
2+
3+
import (
4+
"fmt"
5+
"net/url"
6+
"sort"
7+
"strings"
8+
"time"
9+
10+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
11+
"github.com/hashicorp/terraform-provider-google/google/tpgresource"
12+
transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
13+
)
14+
15+
type MavenArtifact struct {
16+
name string
17+
pomUri string
18+
version string
19+
createTime string
20+
updateTime string
21+
}
22+
23+
func DataSourceArtifactRegistryMavenArtifact() *schema.Resource {
24+
return &schema.Resource{
25+
Read: DataSourceArtifactRegistryMavenArtifactRead,
26+
27+
Schema: map[string]*schema.Schema{
28+
"project": {
29+
Type: schema.TypeString,
30+
Optional: true,
31+
},
32+
"location": {
33+
Type: schema.TypeString,
34+
Required: true,
35+
},
36+
"repository_id": {
37+
Type: schema.TypeString,
38+
Required: true,
39+
},
40+
"group_id": {
41+
Type: schema.TypeString,
42+
Required: true,
43+
},
44+
"artifact_id": {
45+
Type: schema.TypeString,
46+
Required: true,
47+
},
48+
"name": {
49+
Type: schema.TypeString,
50+
Computed: true,
51+
},
52+
"pom_uri": {
53+
Type: schema.TypeString,
54+
Computed: true,
55+
},
56+
"version": {
57+
Type: schema.TypeString,
58+
Computed: true,
59+
},
60+
"create_time": {
61+
Type: schema.TypeString,
62+
Computed: true,
63+
},
64+
"update_time": {
65+
Type: schema.TypeString,
66+
Computed: true,
67+
},
68+
},
69+
}
70+
}
71+
72+
func DataSourceArtifactRegistryMavenArtifactRead(d *schema.ResourceData, meta interface{}) error {
73+
config := meta.(*transport_tpg.Config)
74+
userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent)
75+
if err != nil {
76+
return err
77+
}
78+
79+
project, err := tpgresource.GetProject(d, config)
80+
if err != nil {
81+
return err
82+
}
83+
84+
var res MavenArtifact
85+
86+
artifactId, version := parseMavenArtifact(d.Get("artifact_id").(string))
87+
88+
groupId := d.Get("group_id").(string)
89+
90+
packageName := fmt.Sprintf("%s:%s", groupId, artifactId)
91+
92+
if version != "" {
93+
// fetch package by version
94+
// https://cloud.google.com/artifact-registry/docs/reference/rest/v1/projects.locations.repositories.mavenArtifacts/get
95+
packageUrlSafe := url.QueryEscape(packageName)
96+
urlRequest, err := tpgresource.ReplaceVars(d, config, fmt.Sprintf("{{ArtifactRegistryBasePath}}projects/{{project}}/locations/{{location}}/repositories/{{repository_id}}/mavenArtifacts/%s:%s", packageUrlSafe, version))
97+
if err != nil {
98+
return fmt.Errorf("Error setting api endpoint")
99+
}
100+
101+
resGet, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
102+
Config: config,
103+
Method: "GET",
104+
RawURL: urlRequest,
105+
UserAgent: userAgent,
106+
})
107+
if err != nil {
108+
return err
109+
}
110+
111+
res = convertMavenArtifactResponseToStruct(resGet)
112+
} else {
113+
// fetch the list of packages, ordered by update time
114+
// https://cloud.google.com/artifact-registry/docs/reference/rest/v1/projects.locations.repositories.mavenArtifacts/list
115+
urlRequest, err := tpgresource.ReplaceVars(d, config, "{{ArtifactRegistryBasePath}}projects/{{project}}/locations/{{location}}/repositories/{{repository_id}}/mavenArtifacts")
116+
if err != nil {
117+
return fmt.Errorf("Error setting api endpoint")
118+
}
119+
120+
// to reduce the number of pages we need to fetch, we set the pageSize to 1000(max)
121+
urlRequest, err = transport_tpg.AddQueryParams(urlRequest, map[string]string{"pageSize": "1000"})
122+
if err != nil {
123+
return err
124+
}
125+
126+
res, err = retrieveAndFilterMavenArtifacts(d, config, urlRequest, userAgent, groupId, artifactId, version)
127+
if err != nil {
128+
return err
129+
}
130+
}
131+
132+
// Set Terraform schema fields
133+
if err := d.Set("project", project); err != nil {
134+
return err
135+
}
136+
if err := d.Set("name", res.name); err != nil {
137+
return err
138+
}
139+
if err := d.Set("pom_uri", res.pomUri); err != nil {
140+
return err
141+
}
142+
if err := d.Set("version", res.version); err != nil {
143+
return err
144+
}
145+
if err := d.Set("create_time", res.createTime); err != nil {
146+
return err
147+
}
148+
if err := d.Set("update_time", res.updateTime); err != nil {
149+
return err
150+
}
151+
152+
d.SetId(res.name)
153+
154+
return nil
155+
}
156+
157+
func parseMavenArtifact(pkg string) (artifactId string, version string) {
158+
splitByColon := strings.Split(pkg, ":")
159+
160+
if len(splitByColon) == 2 {
161+
artifactId = splitByColon[0]
162+
version = splitByColon[1]
163+
} else {
164+
artifactId = pkg
165+
}
166+
167+
return artifactId, version
168+
}
169+
170+
func retrieveAndFilterMavenArtifacts(d *schema.ResourceData, config *transport_tpg.Config, urlRequest string, userAgent string, groupId string, artifactId string, version string) (MavenArtifact, error) {
171+
// Paging through the list method until either:
172+
// if a version was provided, the matching package name and version pair
173+
// otherwise, return the first matching package name
174+
175+
var allPackages []MavenArtifact
176+
177+
for {
178+
resListMavenArtifacts, token, err := retrieveListOfMavenArtifacts(config, urlRequest, userAgent)
179+
if err != nil {
180+
return MavenArtifact{}, err
181+
}
182+
183+
for _, pkg := range resListMavenArtifacts {
184+
if strings.Contains(pkg.name, "/"+url.QueryEscape(groupId)+":"+url.QueryEscape(artifactId)+":") {
185+
allPackages = append(allPackages, pkg)
186+
}
187+
}
188+
189+
if token == "" {
190+
break
191+
}
192+
193+
urlRequest, err = transport_tpg.AddQueryParams(urlRequest, map[string]string{"pageToken": token})
194+
if err != nil {
195+
return MavenArtifact{}, err
196+
}
197+
}
198+
199+
if len(allPackages) == 0 {
200+
return MavenArtifact{}, fmt.Errorf("Requested Maven package was not found.")
201+
}
202+
203+
// Client-side sort by updateTime descending (latest first)
204+
sort.Slice(allPackages, func(i, j int) bool {
205+
// Parse RFC3339 timestamps, fallback to string compare if parse fails
206+
ti, err1 := time.Parse(time.RFC3339, allPackages[i].updateTime)
207+
tj, err2 := time.Parse(time.RFC3339, allPackages[j].updateTime)
208+
if err1 == nil && err2 == nil {
209+
return ti.After(tj)
210+
}
211+
return allPackages[i].updateTime > allPackages[j].updateTime
212+
})
213+
214+
if version != "" {
215+
for _, pkg := range allPackages {
216+
if pkg.version == version {
217+
return pkg, nil
218+
}
219+
}
220+
return MavenArtifact{}, fmt.Errorf("Requested version was not found.")
221+
}
222+
223+
// Return the latest package if no version specified
224+
return allPackages[0], nil
225+
}
226+
227+
func retrieveListOfMavenArtifacts(config *transport_tpg.Config, urlRequest string, userAgent string) ([]MavenArtifact, string, error) {
228+
resList, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
229+
Config: config,
230+
Method: "GET",
231+
RawURL: urlRequest,
232+
UserAgent: userAgent,
233+
})
234+
if err != nil {
235+
return make([]MavenArtifact, 0), "", err
236+
}
237+
238+
if nextPageToken, ok := resList["nextPageToken"].(string); ok {
239+
return flattenMavenArtifactDataSourceListResponse(resList), nextPageToken, nil
240+
} else {
241+
return flattenMavenArtifactDataSourceListResponse(resList), "", nil
242+
}
243+
}
244+
245+
func flattenMavenArtifactDataSourceListResponse(res map[string]interface{}) []MavenArtifact {
246+
var mavenArtifacts []MavenArtifact
247+
248+
resMavenArtifacts, _ := res["mavenArtifacts"].([]interface{})
249+
250+
for _, resPackage := range resMavenArtifacts {
251+
pkg, _ := resPackage.(map[string]interface{})
252+
mavenArtifacts = append(mavenArtifacts, convertMavenArtifactResponseToStruct(pkg))
253+
}
254+
255+
return mavenArtifacts
256+
}
257+
258+
func convertMavenArtifactResponseToStruct(res map[string]interface{}) MavenArtifact {
259+
var mavenArtifact MavenArtifact
260+
261+
if name, ok := res["name"].(string); ok {
262+
mavenArtifact.name = name
263+
}
264+
265+
if pomUri, ok := res["pomUri"].(string); ok {
266+
mavenArtifact.pomUri = pomUri
267+
}
268+
269+
if version, ok := res["version"].(string); ok {
270+
mavenArtifact.version = version
271+
}
272+
273+
if createTime, ok := res["createTime"].(string); ok {
274+
mavenArtifact.createTime = createTime
275+
}
276+
277+
if updateTime, ok := res["updateTime"].(string); ok {
278+
mavenArtifact.updateTime = updateTime
279+
}
280+
281+
return mavenArtifact
282+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package artifactregistry_test
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
8+
"github.com/hashicorp/terraform-plugin-testing/terraform"
9+
"github.com/hashicorp/terraform-provider-google/google/acctest"
10+
)
11+
12+
func TestAccDataSourceArtifactRegistryMavenArtifact_basic(t *testing.T) {
13+
acctest.SkipIfVcr(t)
14+
t.Parallel()
15+
16+
// At the moment there are no public Maven artifacts available in Artifact Registry.
17+
// This test is skipped to avoid unnecessary failures.
18+
// As soon as there are public artifacts available, this test can be enabled by removing the skip and adjusting the configuration accordingly.
19+
t.Skip("No public Maven artifacts available in Artifact Registry")
20+
21+
resourceName := "data.google_artifact_registry_maven_artifact.test"
22+
23+
acctest.VcrTest(t, resource.TestCase{
24+
PreCheck: func() { acctest.AccTestPreCheck(t) },
25+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
26+
Steps: []resource.TestStep{
27+
{
28+
Config: testAccDataSourceArtifactRegistryMavenArtifactConfig,
29+
Check: resource.ComposeTestCheckFunc(
30+
resource.TestCheckResourceAttrSet(resourceName, "project"),
31+
resource.TestCheckResourceAttrSet(resourceName, "location"),
32+
resource.TestCheckResourceAttrSet(resourceName, "repository_id"),
33+
resource.TestCheckResourceAttrSet(resourceName, "artifact_id"),
34+
resource.TestCheckResourceAttrSet(resourceName, "name"),
35+
validateMavenArtifactTimestamps(resourceName),
36+
),
37+
},
38+
},
39+
})
40+
}
41+
42+
const testAccDataSourceArtifactRegistryMavenArtifactConfig = `
43+
data "google_artifact_registry_maven_artifact" "test" {
44+
project = "example-project"
45+
location = "us"
46+
repository_id = "example-repo"
47+
group_id = "com.example"
48+
artifact_id = "example-artifact"
49+
}
50+
`
51+
52+
func validateMavenArtifactTimestamps(dataSourceName string) resource.TestCheckFunc {
53+
return func(s *terraform.State) error {
54+
res, ok := s.RootModule().Resources[dataSourceName]
55+
if !ok {
56+
return fmt.Errorf("can't find %s in state", dataSourceName)
57+
}
58+
59+
for _, attr := range []string{"create_time", "update_time"} {
60+
if ts, ok := res.Primary.Attributes[attr]; !ok || !isRFC3339(ts) {
61+
return fmt.Errorf("%s is not RFC3339: %s", attr, ts)
62+
}
63+
}
64+
65+
return nil
66+
}
67+
}

0 commit comments

Comments
 (0)