Skip to content

Commit 97e7160

Browse files
Add singular data source for retrieving a Python package from an Artifact Registry repository (#14611) (#10671)
[upstream:158793fc764fc999dd281df93ee7a56332ccaf53] Signed-off-by: Modular Magician <[email protected]>
1 parent 5bb33e9 commit 97e7160

File tree

5 files changed

+455
-0
lines changed

5 files changed

+455
-0
lines changed

.changelog/14611.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:new-datasource
2+
`google_artifact_registry_python_package`
3+
```

google-beta/provider/provider_mmv1_resources.go

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

0 commit comments

Comments
 (0)