pax_global_header00006660000000000000000000000064146742657470014540gustar00rootroot0000000000000052 comment=24bed2946f49b1bdf6e57aa278235bfb396123c2 golang-github-gophercloud-utils-0.0~git20231010.80377ec/000077500000000000000000000000001467426574700223075ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/.github/000077500000000000000000000000001467426574700236475ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/.github/workflows/000077500000000000000000000000001467426574700257045ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/.github/workflows/unit.yml000066400000000000000000000016241467426574700274110ustar00rootroot00000000000000on: [push, pull_request] name: Unit Testing jobs: test: runs-on: ubuntu-20.04 strategy: fail-fast: false matrix: go-version: - "1.15" env: GO111MODULE: "on" steps: - name: Setup Go ${{ matrix.go-version }} uses: actions/setup-go@v2 with: go-version: ${{ matrix.go-version }} - uses: actions/checkout@v2 - name: Setup environment run: | # Changing into a different directory to avoid polluting go.sum with "go get" cd "$(mktemp -d)" go mod init unit_tests # Pin to a version that Go v1.15 supports go get golang.org/x/tools/cmd/goimports@v0.8.0 - name: Run go vet run: | go vet ./... - name: Run unit tests run: | go test -v ./... - name: Check for formatting run: ./script/format golang-github-gophercloud-utils-0.0~git20231010.80377ec/.gitignore000066400000000000000000000000411467426574700242720ustar00rootroot00000000000000**/*.swp .idea .vscode debug.testgolang-github-gophercloud-utils-0.0~git20231010.80377ec/LICENSE000066400000000000000000000261351467426574700233230ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} 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. golang-github-gophercloud-utils-0.0~git20231010.80377ec/acceptance/000077500000000000000000000000001467426574700243755ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/acceptance/clients/000077500000000000000000000000001467426574700260365ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/acceptance/clients/clients.go000066400000000000000000000023371467426574700300330ustar00rootroot00000000000000// Package clients contains functions for creating service clients // for utils services. // That clients can be used in acceptance tests. package clients import ( "net/http" "os" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack" "github.com/gophercloud/utils/client" "github.com/gophercloud/utils/env" "github.com/gophercloud/utils/gnocchi" "github.com/gophercloud/utils/openstack/clientconfig" ) // NewGnocchiV1Client returns a *ServiceClient for making calls // to the Gnocchi v1 API. // An error will be returned if authentication or client // creation was not possible. func NewGnocchiV1Client() (*gophercloud.ServiceClient, error) { ao, err := clientconfig.AuthOptions(nil) if err != nil { return nil, err } provider, err := openstack.NewClient(ao.IdentityEndpoint) if err != nil { return nil, err } if os.Getenv("OS_DEBUG") != "" { provider.HTTPClient = http.Client{ Transport: &client.RoundTripper{ Rt: &http.Transport{}, Logger: &client.DefaultLogger{}, }, } } err = openstack.Authenticate(provider, *ao) if err != nil { return nil, err } return gnocchi.NewGnocchiV1(provider, gophercloud.EndpointOpts{ Region: env.Getenv("OS_REGION_NAME"), }) } golang-github-gophercloud-utils-0.0~git20231010.80377ec/acceptance/gnocchi/000077500000000000000000000000001467426574700260075ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/acceptance/gnocchi/metric/000077500000000000000000000000001467426574700272725ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/acceptance/gnocchi/metric/v1/000077500000000000000000000000001467426574700276205ustar00rootroot00000000000000archivepolicies.go000066400000000000000000000031751467426574700332470ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/acceptance/gnocchi/metric/v1package v1 import ( "testing" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/utils/gnocchi/metric/v1/archivepolicies" ) // CreateArchivePolicy will create a Gnocchi archive policy. An error will be returned if the // archive policy could not be created. func CreateArchivePolicy(t *testing.T, client *gophercloud.ServiceClient) (*archivepolicies.ArchivePolicy, error) { policyName := tools.RandomString("TESTACCT-", 8) createOpts := archivepolicies.CreateOpts{ Name: policyName, AggregationMethods: []string{ "mean", "sum", }, Definition: []archivepolicies.ArchivePolicyDefinitionOpts{ { Granularity: "1:00:00", TimeSpan: "30 days, 0:00:00", }, { Granularity: "24:00:00", TimeSpan: "90 days, 0:00:00", }, }, } t.Logf("Attempting to create a Gnocchi archive policy") archivePolicy, err := archivepolicies.Create(client, createOpts).Extract() if err != nil { return nil, err } t.Logf("Successfully created the Gnocchi archive policy.") return archivePolicy, nil } // DeleteArchivePolicy will delete a Gnocchi archive policy. // A fatal error will occur if the delete was not successful. func DeleteArchivePolicy(t *testing.T, client *gophercloud.ServiceClient, archivePolicyName string) { t.Logf("Attempting to delete the Gnocchi archive policy: %s", archivePolicyName) err := archivepolicies.Delete(client, archivePolicyName).ExtractErr() if err != nil { t.Fatalf("Unable to delete the Gnocchi archive policy %s: %v", archivePolicyName, err) } t.Logf("Deleted the Gnocchi archive policy: %s", archivePolicyName) } archivepolicies_test.go000066400000000000000000000034521467426574700343040ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/acceptance/gnocchi/metric/v1//go:build acceptance || metric || archivepolicies // +build acceptance metric archivepolicies package v1 import ( "testing" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/utils/acceptance/clients" "github.com/gophercloud/utils/gnocchi/metric/v1/archivepolicies" ) func TestArchivePoliciesCRUD(t *testing.T) { client, err := clients.NewGnocchiV1Client() if err != nil { t.Fatalf("Unable to create a Gnocchi client: %v", err) } archivePolicy, err := CreateArchivePolicy(t, client) if err != nil { t.Fatalf("Unable to create a Gnocchi archive policy: %v", err) } defer DeleteArchivePolicy(t, client, archivePolicy.Name) tools.PrintResource(t, archivePolicy) updateOpts := archivepolicies.UpdateOpts{ Definition: []archivepolicies.ArchivePolicyDefinitionOpts{ { Granularity: "1:00:00", TimeSpan: "90 days, 0:00:00", }, { Granularity: "24:00:00", TimeSpan: "365 days, 0:00:00", }, }, } t.Logf("Attempting to update an archive policy %s", archivePolicy.Name) newArchivePolicy, err := archivepolicies.Update(client, archivePolicy.Name, updateOpts).Extract() if err != nil { t.Fatalf("Unable to update a Gnocchi archive policy: %v", err) } tools.PrintResource(t, newArchivePolicy) } func TestArchivePoliciesList(t *testing.T) { client, err := clients.NewGnocchiV1Client() if err != nil { t.Fatalf("Unable to create a Gnocchi client: %v", err) } allPages, err := archivepolicies.List(client).AllPages() if err != nil { t.Fatalf("Unable to list archive policies: %v", err) } allArchivePolicies, err := archivepolicies.ExtractArchivePolicies(allPages) if err != nil { t.Fatalf("Unable to extract archive policies: %v", err) } for _, archivePolicy := range allArchivePolicies { tools.PrintResource(t, archivePolicy) } } golang-github-gophercloud-utils-0.0~git20231010.80377ec/acceptance/gnocchi/metric/v1/measures.go000066400000000000000000000106631467426574700320010ustar00rootroot00000000000000package v1 import ( "testing" "time" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/utils/gnocchi/metric/v1/measures" ) // CreateMeasures will create measures inside a single Gnocchi metric. An error will be returned if the // measures could not be created. func CreateMeasures(t *testing.T, client *gophercloud.ServiceClient, metricID string) error { currentTimestamp := time.Now().UTC() pastHourTimestamp := currentTimestamp.Add(-1 * time.Hour) currentValue := float64(tools.RandomInt(100, 200)) pastHourValue := float64(tools.RandomInt(500, 600)) measuresToCreate := []measures.MeasureOpts{ { Timestamp: ¤tTimestamp, Value: currentValue, }, { Timestamp: &pastHourTimestamp, Value: pastHourValue, }, } createOpts := measures.CreateOpts{ Measures: measuresToCreate, } t.Logf("Attempting to create measures inside a Gnocchi metric %s", metricID) if err := measures.Create(client, metricID, createOpts).ExtractErr(); err != nil { return err } t.Logf("Successfully created measures inside the Gnocchi metric %s", metricID) return nil } // MeasuresBatchCreateMetrics will create measures inside different metrics via batch request. // An error will be returned if measures could not be created. func MeasuresBatchCreateMetrics(t *testing.T, client *gophercloud.ServiceClient, metricIDs ...string) error { currentTimestamp := time.Now().UTC() pastHourTimestamp := currentTimestamp.Add(-1 * time.Hour) currentValue := float64(tools.RandomInt(100, 200)) pastHourValue := float64(tools.RandomInt(500, 600)) createOpts := make([]measures.MetricOpts, len(metricIDs)) // Populate batch options with provided metric IDs and generated values. for i, m := range metricIDs { createOpts[i] = measures.MetricOpts{ ID: m, Measures: []measures.MeasureOpts{ { Timestamp: ¤tTimestamp, Value: currentValue, }, { Timestamp: &pastHourTimestamp, Value: pastHourValue, }, }, } } t.Logf("Attempting to create measures inside Gnocchi metrics via batch request") if err := measures.BatchCreateMetrics(client, createOpts).ExtractErr(); err != nil { return err } t.Logf("Successfully created measures inside Gnocchi metrics") return nil } // MeasuresBatchCreateResourcesMetrics will create measures inside different metrics via batch request to resource IDs. // The batchResourcesMetrics arguments is a mapping between resource IDs and corresponding metric names. // An error will be returned if measures could not be created. func MeasuresBatchCreateResourcesMetrics(t *testing.T, client *gophercloud.ServiceClient, batchResourcesMetrics map[string][]string) error { currentTimestamp := time.Now().UTC() pastHourTimestamp := currentTimestamp.Add(-1 * time.Hour) currentValue := float64(tools.RandomInt(100, 200)) pastHourValue := float64(tools.RandomInt(500, 600)) // measureSet is a set of measures for an every metric. measureSet := []measures.MeasureOpts{ { Timestamp: ¤tTimestamp, Value: currentValue, }, { Timestamp: &pastHourTimestamp, Value: pastHourValue, }, } // batchResourcesMetricsOpts is an internal slice representation of measures.BatchResourcesMetricsOpts stucts. batchResourcesMetricsOpts := make([]measures.BatchResourcesMetricsOpts, 0) for resourceID, metricNames := range batchResourcesMetrics { // resourcesMetricsOpts is an internal slice representation of measures.ResourcesMetricsOpts structs. resourcesMetricsOpts := make([]measures.ResourcesMetricsOpts, 0) // Populate batch options for each metric of a resource. for _, metricName := range metricNames { resourcesMetricsOpts = append(resourcesMetricsOpts, measures.ResourcesMetricsOpts{ MetricName: metricName, Measures: measureSet, }) } // Save batch options of a resource. batchResourcesMetricsOpts = append(batchResourcesMetricsOpts, measures.BatchResourcesMetricsOpts{ ResourceID: resourceID, ResourcesMetrics: resourcesMetricsOpts, }) } createOpts := measures.BatchCreateResourcesMetricsOpts{ CreateMetrics: true, BatchResourcesMetrics: batchResourcesMetricsOpts, } t.Logf("Attempting to create measures inside Gnocchi metrics via batch request with resource IDs") if err := measures.BatchCreateResourcesMetrics(client, createOpts).ExtractErr(); err != nil { return err } t.Logf("Successfully created measures inside Gnocchi metrics") return nil } measures_test.go000066400000000000000000000073501467426574700327600ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/acceptance/gnocchi/metric/v1//go:build acceptance || metric || measures // +build acceptance metric measures package v1 import ( "testing" "github.com/gophercloud/utils/acceptance/clients" "github.com/gophercloud/utils/gnocchi/metric/v1/measures" ) func TestMeasuresCRUD(t *testing.T) { client, err := clients.NewGnocchiV1Client() if err != nil { t.Fatalf("Unable to create a Gnocchi client: %v", err) } // Create a single metric to test Create measures request. metric, err := CreateMetric(t, client) if err != nil { t.Fatalf("Unable to create a Gnocchi metric: %v", err) } defer DeleteMetric(t, client, metric.ID) // Test Create measures request. if err := CreateMeasures(t, client, metric.ID); err != nil { t.Fatalf("Unable to create measures inside the Gnocchi metric: %v", err) } // Check created measures. listOpts := measures.ListOpts{ Refresh: true, } allPages, err := measures.List(client, metric.ID, listOpts).AllPages() if err != nil { t.Fatalf("Unable to list measures of the metric %s: %v", metric.ID, err) } metricMeasures, err := measures.ExtractMeasures(allPages) if err != nil { t.Fatalf("Unable to extract measures: %v", metricMeasures) } t.Log(metricMeasures) } func TestMeasuresBatchCreateMetrics(t *testing.T) { client, err := clients.NewGnocchiV1Client() if err != nil { t.Fatalf("Unable to create a Gnocchi client: %v", err) } // Create a couple of metrics to test BatchCreateMetrics requets. metricToBatchOne, err := CreateMetric(t, client) if err != nil { t.Fatalf("Unable to create a Gnocchi metric: %v", err) } defer DeleteMetric(t, client, metricToBatchOne.ID) metricToBatchTwo, err := CreateMetric(t, client) if err != nil { t.Fatalf("Unable to create a Gnocchi metric: %v", err) } defer DeleteMetric(t, client, metricToBatchTwo.ID) // Test create batch request based on metrics IDs. if err := MeasuresBatchCreateMetrics(t, client, metricToBatchOne.ID, metricToBatchTwo.ID); err != nil { t.Fatalf("Unable to create measures inside Gnocchi metrics: %v", err) } // Check measures of each metric after the BatchMetrics request. listOpts := measures.ListOpts{ Refresh: true, } allPagesMetricOne, err := measures.List(client, metricToBatchOne.ID, listOpts).AllPages() if err != nil { t.Fatalf("Unable to list measures of the metric %s: %v", metricToBatchOne.ID, err) } metricOneMeasures, err := measures.ExtractMeasures(allPagesMetricOne) if err != nil { t.Fatalf("Unable to extract measures: %v", metricOneMeasures) } t.Logf("Measures for the metric: %s, %v", metricToBatchOne.ID, metricOneMeasures) allPagesMetricTwo, err := measures.List(client, metricToBatchTwo.ID, listOpts).AllPages() if err != nil { t.Fatalf("Unable to list measures of the metric %s: %v", metricToBatchTwo.ID, err) } metricTwoMeasures, err := measures.ExtractMeasures(allPagesMetricTwo) if err != nil { t.Fatalf("Unable to extract measures: %v", metricTwoMeasures) } t.Logf("Measures for the metric: %s, %v", metricToBatchTwo.ID, metricTwoMeasures) } func TestMeasuresBatchCreateResourcesMetrics(t *testing.T) { client, err := clients.NewGnocchiV1Client() if err != nil { t.Fatalf("Unable to create a Gnocchi client: %v", err) } // Create a couple of resources with metrics to test BatchCreateResourcesMetrics requets. batchResourcesMetrics, err := CreateResourcesToBatchMeasures(t, client) if err != nil { t.Fatalf("Unable to create Gnocchi resources and metrics: %v", err) } // Test create batch request based on resource IDs. if err := MeasuresBatchCreateResourcesMetrics(t, client, batchResourcesMetrics); err != nil { t.Fatalf("Unable to create measures inside Gnocchi metrics: %v", err) } // Delete resources. for resourceID := range batchResourcesMetrics { DeleteResource(t, client, "generic", resourceID) } } golang-github-gophercloud-utils-0.0~git20231010.80377ec/acceptance/gnocchi/metric/v1/metrics.go000066400000000000000000000027011467426574700316150ustar00rootroot00000000000000package v1 import ( "testing" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/utils/gnocchi/metric/v1/metrics" ) // CreateMetric will create Gnocchi metric. An error will be returned if the // metric could not be created. func CreateMetric(t *testing.T, client *gophercloud.ServiceClient) (*metrics.Metric, error) { // Metric will be created assuming that your Gnocchi's indexer installation was configured with // the "gnocchi-manage --noskip-archive-policies-creation" command. So Gnocchi has the default policies: // "low", "medium", "high", "bool". name := tools.RandomString("TESTACCT-", 8) createOpts := metrics.CreateOpts{ ArchivePolicyName: "low", Name: name, } t.Logf("Attempting to create a Gnocchi metric") metric, err := metrics.Create(client, createOpts).Extract() if err != nil { return nil, err } t.Logf("Successfully created the Gnocchi metric.") return metric, nil } // DeleteMetric will delete a Gnocchi metric with a specified ID. // A fatal error will occur if the delete was not successful. func DeleteMetric(t *testing.T, client *gophercloud.ServiceClient, metricID string) { t.Logf("Attempting to delete the Gnocchi metric: %s", metricID) err := metrics.Delete(client, metricID).ExtractErr() if err != nil { t.Fatalf("Unable to delete the Gnocchi metric %s: %v", metricID, err) } t.Logf("Deleted the Gnocchi metric: %s", metricID) } golang-github-gophercloud-utils-0.0~git20231010.80377ec/acceptance/gnocchi/metric/v1/metrics_test.go000066400000000000000000000031461467426574700326600ustar00rootroot00000000000000//go:build acceptance || metric || metrics // +build acceptance metric metrics package v1 import ( "testing" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/utils/acceptance/clients" "github.com/gophercloud/utils/gnocchi/metric/v1/metrics" ) func TestMetricsCRUD(t *testing.T) { client, err := clients.NewGnocchiV1Client() if err != nil { t.Fatalf("Unable to create a Gnocchi client: %v", err) } metric, err := CreateMetric(t, client) if err != nil { t.Fatalf("Unable to create a Gnocchi metric: %v", err) } defer DeleteMetric(t, client, metric.ID) tools.PrintResource(t, metric) } func TestMetricsList(t *testing.T) { client, err := clients.NewGnocchiV1Client() if err != nil { t.Fatalf("Unable to create a Gnocchi client: %v", err) } metric1, err := CreateMetric(t, client) if err != nil { t.Fatalf("Unable to create a Gnocchi metric: %v", err) } defer DeleteMetric(t, client, metric1.ID) metric2, err := CreateMetric(t, client) if err != nil { t.Fatalf("Unable to create a Gnocchi metric: %v", err) } defer DeleteMetric(t, client, metric2.ID) metric3, err := CreateMetric(t, client) if err != nil { t.Fatalf("Unable to create a Gnocchi metric: %v", err) } defer DeleteMetric(t, client, metric3.ID) listOpts := metrics.ListOpts{} allPages, err := metrics.List(client, listOpts).AllPages() if err != nil { t.Fatalf("Unable to list metrics: %v", err) } allMetrics, err := metrics.ExtractMetrics(allPages) if err != nil { t.Fatalf("Unable to extract metrics: %v", err) } for _, metric := range allMetrics { tools.PrintResource(t, metric) } } golang-github-gophercloud-utils-0.0~git20231010.80377ec/acceptance/gnocchi/metric/v1/resources.go000066400000000000000000000101261467426574700321610ustar00rootroot00000000000000package v1 import ( "testing" "time" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/utils/gnocchi/metric/v1/resources" "github.com/hashicorp/go-uuid" ) // CreateGenericResource will create a Gnocchi resource with a generic type. // An error will be returned if the resource could not be created. func CreateGenericResource(t *testing.T, client *gophercloud.ServiceClient) (*resources.Resource, error) { id, err := uuid.GenerateUUID() if err != nil { return nil, err } randomDay := tools.RandomInt(1, 100) now := time.Now().UTC().AddDate(0, 0, -randomDay) metricName := tools.RandomString("TESTACCT-", 8) createOpts := resources.CreateOpts{ ID: id, StartedAt: &now, Metrics: map[string]interface{}{ metricName: map[string]string{ "archive_policy_name": "medium", }, }, } resourceType := "generic" t.Logf("Attempting to create a generic Gnocchi resource") resource, err := resources.Create(client, resourceType, createOpts).Extract() if err != nil { return nil, err } t.Logf("Successfully created the generic Gnocchi resource.") return resource, nil } // DeleteResource will delete a Gnocchi resource with specified type and ID. // A fatal error will occur if the delete was not successful. func DeleteResource(t *testing.T, client *gophercloud.ServiceClient, resourceType, resourceID string) { t.Logf("Attempting to delete the Gnocchi resource: %s", resourceID) err := resources.Delete(client, resourceType, resourceID).ExtractErr() if err != nil { t.Fatalf("Unable to delete the Gnocchi resource %s: %v", resourceID, err) } t.Logf("Deleted the Gnocchi resource: %s", resourceID) } // CreateResourcesToBatchMeasures will create Gnocchi resources with metrics to test batch measures requests and // return a map with references of resource IDs and metric names. // An error will be returned if resources or metrics could not be created. func CreateResourcesToBatchMeasures(t *testing.T, client *gophercloud.ServiceClient) (map[string][]string, error) { // Prepare metric names. firstMetricName := tools.RandomString("TESTACCT-", 8) secondMetricName := tools.RandomString("TESTACCT-", 8) thirdMetricName := tools.RandomString("TESTACCT-", 8) // Prepare the first resource. firstResourceID, err := uuid.GenerateUUID() if err != nil { return nil, err } firstRandomDay := tools.RandomInt(1, 100) firstStartTimestamp := time.Now().UTC().AddDate(0, 0, -firstRandomDay) firstResourceCreateOpts := resources.CreateOpts{ ID: firstResourceID, StartedAt: &firstStartTimestamp, Metrics: map[string]interface{}{ firstMetricName: map[string]string{ "archive_policy_name": "medium", }, secondMetricName: map[string]string{ "archive_policy_name": "low", }, }, } firstResourceType := "generic" t.Logf("Attempting to create a generic Gnocchi resource") firstResource, err := resources.Create(client, firstResourceType, firstResourceCreateOpts).Extract() if err != nil { return nil, err } t.Logf("Successfully created the generic Gnocchi resource.") tools.PrintResource(t, firstResource) // Prepare the second resource. secondResourceID, err := uuid.GenerateUUID() if err != nil { return nil, err } secondRandomDay := tools.RandomInt(1, 100) secondStartTimestamp := time.Now().UTC().AddDate(0, 0, -secondRandomDay) secondResourceCreateOpts := resources.CreateOpts{ ID: secondResourceID, StartedAt: &secondStartTimestamp, Metrics: map[string]interface{}{ thirdMetricName: map[string]string{ "archive_policy_name": "low", }, }, } secondResourceType := "generic" t.Logf("Attempting to create a generic Gnocchi resource") secondResource, err := resources.Create(client, secondResourceType, secondResourceCreateOpts).Extract() if err != nil { return nil, err } t.Logf("Successfully created the generic Gnocchi resource.") tools.PrintResource(t, secondResource) resourcesReferenceMap := map[string][]string{ firstResource.ID: { firstMetricName, secondMetricName, }, secondResource.ID: { thirdMetricName, }, } return resourcesReferenceMap, nil } resources_test.go000066400000000000000000000034171467426574700331460ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/acceptance/gnocchi/metric/v1//go:build acceptance || metric || resources // +build acceptance metric resources package v1 import ( "testing" "time" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/utils/acceptance/clients" "github.com/gophercloud/utils/gnocchi/metric/v1/resources" ) func TestResourcesCRUD(t *testing.T) { client, err := clients.NewGnocchiV1Client() if err != nil { t.Fatalf("Unable to create a Gnocchi client: %v", err) } genericResource, err := CreateGenericResource(t, client) if err != nil { t.Fatalf("Unable to create a generic Gnocchi resource: %v", err) } defer DeleteResource(t, client, genericResource.Type, genericResource.ID) tools.PrintResource(t, genericResource) newStartedAt := time.Date(2018, 1, 1, 1, 1, 0, 0, time.UTC) newMetrics := map[string]interface{}{} updateOpts := &resources.UpdateOpts{ StartedAt: &newStartedAt, Metrics: &newMetrics, } t.Logf("Attempting to update a resource %s", genericResource.ID) newGenericResource, err := resources.Update(client, genericResource.Type, genericResource.ID, updateOpts).Extract() if err != nil { t.Fatalf("Unable to update the generic Gnocchi resource: %v", err) } tools.PrintResource(t, newGenericResource) } func TestResourcesList(t *testing.T) { client, err := clients.NewGnocchiV1Client() if err != nil { t.Fatalf("Unable to create a Gnocchi client: %v", err) } opts := resources.ListOpts{} resourceType := "generic" allPages, err := resources.List(client, opts, resourceType).AllPages() if err != nil { t.Fatalf("Unable to list resources: %v", err) } allResources, err := resources.ExtractResources(allPages) if err != nil { t.Fatalf("Unable to extract resources: %v", err) } for _, resource := range allResources { tools.PrintResource(t, resource) } } resourcetypes.go000066400000000000000000000034551467426574700330130ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/acceptance/gnocchi/metric/v1package v1 import ( "testing" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/utils/gnocchi/metric/v1/resourcetypes" ) // CreateResourceType creates Gnocchi resource type. An error will be returned if the // resource type could not be created. func CreateResourceType(t *testing.T, client *gophercloud.ServiceClient) (*resourcetypes.ResourceType, error) { resourceTypeName := tools.RandomString("TESTACCT-", 8) attributeStringName := tools.RandomString("TESTACCT-ATTRIBUTE-", 8) attributeUUIDName := tools.RandomString("TESTACCT-ATTRIBUTE-", 8) createOpts := resourcetypes.CreateOpts{ Name: resourceTypeName, Attributes: map[string]resourcetypes.AttributeOpts{ attributeStringName: { Type: "string", Details: map[string]interface{}{ "max_length": 128, "required": false, }, }, attributeUUIDName: { Type: "uuid", Details: map[string]interface{}{ "required": true, }, }, }, } t.Logf("Attempting to create a Gnocchi resource type") resourceType, err := resourcetypes.Create(client, createOpts).Extract() if err != nil { return nil, err } t.Logf("Successfully created the Gnocchi resource type.") return resourceType, nil } // DeleteResourceType deletes a Gnocchi resource type with the specified name. // A fatal error will occur if the delete was not successful. func DeleteResourceType(t *testing.T, client *gophercloud.ServiceClient, resourceTypeName string) { t.Logf("Attempting to delete the Gnocchi resource type: %s", resourceTypeName) err := resourcetypes.Delete(client, resourceTypeName).ExtractErr() if err != nil { t.Fatalf("Unable to delete the Gnocchi resource type %s: %v", resourceTypeName, err) } t.Logf("Deleted the Gnocchi resource type: %s", resourceTypeName) } resourcetypes_test.go000066400000000000000000000061761467426574700340550ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/acceptance/gnocchi/metric/v1//go:build acceptance || metric || resourcetypes // +build acceptance metric resourcetypes package v1 import ( "testing" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/utils/acceptance/clients" "github.com/gophercloud/utils/gnocchi/metric/v1/resourcetypes" ) func TestResourceTypesList(t *testing.T) { client, err := clients.NewGnocchiV1Client() if err != nil { t.Fatalf("Unable to create a Gnocchi client: %v", err) } allPages, err := resourcetypes.List(client).AllPages() if err != nil { t.Fatalf("Unable to list resource types: %v", err) } allResourceTypes, err := resourcetypes.ExtractResourceTypes(allPages) if err != nil { t.Fatalf("Unable to extract resource types: %v", err) } for _, resourceType := range allResourceTypes { tools.PrintResource(t, resourceType) } } func TestResourceTypesCRUD(t *testing.T) { client, err := clients.NewGnocchiV1Client() if err != nil { t.Fatalf("Unable to create a Gnocchi client: %v", err) } resourceType, err := CreateResourceType(t, client) if err != nil { t.Fatalf("Unable to create a Gnocchi resource type: %v", err) } defer DeleteResourceType(t, client, resourceType.Name) tools.PrintResource(t, resourceType) // Populate attributes that will be deleted. attributesToDelete := []string{} for attributeName := range resourceType.Attributes { attributesToDelete = append(attributesToDelete, attributeName) } // New string attribute parameters. newStringAttributeOpts := resourcetypes.AttributeOpts{ Details: map[string]interface{}{ "required": false, "min_length": 32, "max_length": 64, }, Type: "string", } newStringAttributeOptsName := tools.RandomString("TESTACCT-ATTRIBUTE-", 8) // New datetime attribute parameters. newDatetimeAttributeOpts := resourcetypes.AttributeOpts{ Details: map[string]interface{}{ "required": true, "options": map[string]interface{}{ "fill": "2018-07-28T00:01:01Z", }, }, Type: "datetime", } newDatetimeAttributeOptsName := tools.RandomString("TESTACCT-ATTRIBUTE-", 8) // Initial options for the Update request with the new resource type attributes. updateOpts := resourcetypes.UpdateOpts{ Attributes: []resourcetypes.AttributeUpdateOpts{ { Name: newStringAttributeOptsName, Operation: resourcetypes.AttributeAdd, Value: &newStringAttributeOpts, }, { Name: newDatetimeAttributeOptsName, Operation: resourcetypes.AttributeAdd, Value: &newDatetimeAttributeOpts, }, }, } // Add options to delete attributes. for _, attributeToDelete := range attributesToDelete { updateOpts.Attributes = append(updateOpts.Attributes, resourcetypes.AttributeUpdateOpts{ Name: attributeToDelete, Operation: resourcetypes.AttributeRemove, }) } t.Logf("Attempting to update a Gnocchi resource type \"%s\".", resourceType.Name) newResourceType, err := resourcetypes.Update(client, resourceType.Name, updateOpts).Extract() if err != nil { t.Fatalf("Unable to update a Gnocchi resource type: %v", err) } t.Logf("Successfully updated the Gnocchi resource type \"%s\".", resourceType.Name) tools.PrintResource(t, newResourceType) } golang-github-gophercloud-utils-0.0~git20231010.80377ec/acceptance/openstack/000077500000000000000000000000001467426574700263645ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/acceptance/openstack/clientconfig/000077500000000000000000000000001467426574700310305ustar00rootroot00000000000000clientconfig_test.go000066400000000000000000000041421467426574700350040ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/acceptance/openstack/clientconfig// +build acceptance clientconfig package clientconfig import ( "net/http" "os" "strings" "testing" "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" acc_compute "github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2" acc_tools "github.com/gophercloud/gophercloud/acceptance/tools" osClient "github.com/gophercloud/utils/client" cc "github.com/gophercloud/utils/openstack/clientconfig" ) func TestServerCreateDestroy(t *testing.T) { // This will be populated by environment variables. clientOpts := &cc.ClientOpts{} client, err := cc.NewServiceClient("compute", clientOpts) if err != nil { t.Fatalf("Unable to create client: %v", err) } server, err := acc_compute.CreateServer(t, client) if err != nil { t.Fatalf("Unable to create server: %v", err) } defer acc_compute.DeleteServer(t, client, server) newServer, err := servers.Get(client, server.ID).Extract() if err != nil { t.Fatalf("Unable to get server %s: %v", server.ID, err) } acc_tools.PrintResource(t, newServer) } func TestEndpointType(t *testing.T) { clientOpts := &cc.ClientOpts{ EndpointType: "admin", } client, err := cc.NewServiceClient("identity", clientOpts) if err != nil { t.Fatalf("Unable to create client: %v", err) } if !strings.Contains(client.Endpoint, "35357") { t.Fatalf("Endpoint was not correctly set to admin interface") } } func TestCustomHTTPClient(t *testing.T) { var logger osClient.Logger if os.Getenv("OS_DEBUG") != "" { logger = &osClient.DefaultLogger{} } httpClient := http.Client{ Transport: &osClient.RoundTripper{ Rt: &http.Transport{}, Logger: logger, }, } clientOpts := &cc.ClientOpts{ HTTPClient: &httpClient, } client, err := cc.NewServiceClient("compute", clientOpts) if err != nil { t.Fatalf("Unable to create client: %v", err) } allPages, err := servers.List(client, nil).AllPages() if err != nil { t.Fatalf("Unable to list servers: %v", err) } allServers, err := servers.ExtractServers(allPages) if err != nil { t.Fatalf("Unable to extract servers: %v", err) } for _, v := range allServers { t.Logf("%#v", v) } } golang-github-gophercloud-utils-0.0~git20231010.80377ec/acceptance/openstack/objectstorage/000077500000000000000000000000001467426574700312175ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/acceptance/openstack/objectstorage/v1/000077500000000000000000000000001467426574700315455ustar00rootroot00000000000000objects_test.go000066400000000000000000000426171467426574700345170ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/acceptance/openstack/objectstorage/v1//go:build acceptance // +build acceptance package v1 import ( "bytes" "io/ioutil" "net/http" "os" "path" "testing" "github.com/gophercloud/gophercloud/acceptance/tools" th "github.com/gophercloud/gophercloud/testhelper" "github.com/gophercloud/utils/openstack/clientconfig" "github.com/gophercloud/utils/openstack/objectstorage/v1/objects" ) func TestObjectStreamingUploadDownload(t *testing.T) { client, err := clientconfig.NewServiceClient("object-store", nil) th.AssertNoErr(t, err) // Create a test container to store the object. cName, err := CreateContainer(t, client) th.AssertNoErr(t, err) defer DeleteContainer(t, client, cName) // Generate a random object name and random content. oName := tools.RandomString("test-object-", 8) content := tools.RandomString("", 10) contentBuf := bytes.NewBuffer([]byte(content)) // Upload the object uploadOpts := &objects.UploadOpts{ Content: contentBuf, } uploadResult, err := objects.Upload(client, cName, oName, uploadOpts) defer DeleteObject(t, client, cName, oName) th.AssertNoErr(t, err) th.AssertEquals(t, uploadResult.Success, true) tools.PrintResource(t, uploadResult) // Download the object downloadOpts := &objects.DownloadOpts{ OutFile: "-", } downloadResults, err := objects.Download(client, cName, []string{oName}, downloadOpts) th.AssertNoErr(t, err) th.AssertEquals(t, len(downloadResults), 1) th.AssertEquals(t, downloadResults[0].Success, true) downloadedContent, err := ioutil.ReadAll(downloadResults[0].Content) th.AssertNoErr(t, err) th.AssertEquals(t, string(downloadedContent), content) tools.PrintResource(t, downloadResults[0]) } func TestObjectFileUploadDownload(t *testing.T) { client, err := clientconfig.NewServiceClient("object-store", nil) th.AssertNoErr(t, err) // Create a file with random content source, err := CreateRandomFile(t, "/tmp") th.AssertNoErr(t, err) defer DeleteTempFile(t, source) // Create a destination file. dest := tools.RandomString("/tmp/test-dest-", 8) // Create a random object name. oName := tools.RandomString("test-object-", 8) // Create a test container to store the object. cName, err := CreateContainer(t, client) th.AssertNoErr(t, err) defer DeleteContainer(t, client, cName) // Upload the object uploadOpts := &objects.UploadOpts{ Path: source, } uploadResult, err := objects.Upload(client, cName, oName, uploadOpts) defer DeleteObject(t, client, cName, oName) th.AssertNoErr(t, err) th.AssertEquals(t, uploadResult.Success, true) tools.PrintResource(t, uploadResult) // Download the object downloadOpts := &objects.DownloadOpts{ OutFile: dest, } downloadResults, err := objects.Download(client, cName, []string{oName}, downloadOpts) th.AssertNoErr(t, err) defer DeleteTempFile(t, dest) th.AssertEquals(t, len(downloadResults), 1) th.AssertEquals(t, downloadResults[0].Success, true) tools.PrintResource(t, downloadResults[0]) equals, err := CompareFiles(t, source, dest) th.AssertNoErr(t, err) th.AssertEquals(t, equals, true) } func TestObjectStreamingSLO(t *testing.T) { client, err := clientconfig.NewServiceClient("object-store", nil) th.AssertNoErr(t, err) // Create a test container to store the object. cName, err := CreateContainer(t, client) th.AssertNoErr(t, err) defer DeleteContainer(t, client, cName) defer DeleteContainer(t, client, cName+"_segments") // Generate a random object name and random content. oName := tools.RandomString("test-object-", 8) content := tools.RandomString("", 256) contentBuf := bytes.NewBuffer([]byte(content)) // Upload the object uploadOpts := &objects.UploadOpts{ Checksum: true, Content: contentBuf, SegmentSize: 62, UseSLO: true, } uploadResult, err := objects.Upload(client, cName, oName, uploadOpts) defer DeleteObject(t, client, cName, oName) th.AssertNoErr(t, err) th.AssertEquals(t, uploadResult.Success, true) tools.PrintResource(t, uploadResult) // Download the object downloadOpts := &objects.DownloadOpts{ OutFile: "-", } downloadResults, err := objects.Download(client, cName, []string{oName}, downloadOpts) th.AssertNoErr(t, err) th.AssertEquals(t, len(downloadResults), 1) th.AssertEquals(t, downloadResults[0].Success, true) tools.PrintResource(t, downloadResults[0]) // Compare the downloaded content with the uploaded. downloadedContent, err := ioutil.ReadAll(downloadResults[0].Content) th.AssertNoErr(t, err) th.AssertEquals(t, string(downloadedContent), content) // Replace the object with the same object. contentBuf = bytes.NewBuffer([]byte(content)) uploadOpts.Content = contentBuf uploadResult, err = objects.Upload(client, cName, oName, uploadOpts) th.AssertNoErr(t, err) th.AssertEquals(t, uploadResult.Success, true) tools.PrintResource(t, uploadResult) // Download the object downloadResults, err = objects.Download(client, cName, []string{oName}, downloadOpts) th.AssertNoErr(t, err) th.AssertEquals(t, len(downloadResults), 1) th.AssertEquals(t, downloadResults[0].Success, true) tools.PrintResource(t, downloadResults[0]) // Compare the downloaded content with the uploaded. downloadedContent, err = ioutil.ReadAll(downloadResults[0].Content) th.AssertNoErr(t, err) th.AssertEquals(t, string(downloadedContent), content) } func TestObjectFileSLO(t *testing.T) { client, err := clientconfig.NewServiceClient("object-store", nil) th.AssertNoErr(t, err) // Create a file with random content source, err := CreateRandomFile(t, "/tmp") th.AssertNoErr(t, err) defer DeleteTempFile(t, source) // Create a destination file. dest := tools.RandomString("/tmp/test-dest-", 8) defer DeleteTempFile(t, dest) // Create a random object name. oName := tools.RandomString("test-object-", 8) // Create a test container to store the object. cName, err := CreateContainer(t, client) th.AssertNoErr(t, err) defer DeleteContainer(t, client, cName) defer DeleteContainer(t, client, cName+"_segments") // Upload the object uploadOpts := &objects.UploadOpts{ Path: source, SegmentSize: 62, UseSLO: true, } uploadResult, err := objects.Upload(client, cName, oName, uploadOpts) defer DeleteObject(t, client, cName, oName) th.AssertNoErr(t, err) th.AssertEquals(t, uploadResult.Success, true) tools.PrintResource(t, uploadResult) // Download the object downloadOpts := &objects.DownloadOpts{ OutFile: dest, } downloadResults, err := objects.Download(client, cName, []string{oName}, downloadOpts) th.AssertNoErr(t, err) th.AssertEquals(t, len(downloadResults), 1) th.AssertEquals(t, downloadResults[0].Success, true) tools.PrintResource(t, downloadResults[0]) equals, err := CompareFiles(t, source, dest) th.AssertNoErr(t, err) th.AssertEquals(t, equals, true) tools.PrintResource(t, downloadResults[0]) // Replace the object with the same object. uploadResult, err = objects.Upload(client, cName, oName, uploadOpts) th.AssertNoErr(t, err) th.AssertEquals(t, uploadResult.Success, true) tools.PrintResource(t, uploadResult) // Download the object downloadResults, err = objects.Download(client, cName, []string{oName}, downloadOpts) th.AssertNoErr(t, err) th.AssertEquals(t, len(downloadResults), 1) th.AssertEquals(t, downloadResults[0].Success, true) tools.PrintResource(t, downloadResults[0]) equals, err = CompareFiles(t, source, dest) th.AssertNoErr(t, err) th.AssertEquals(t, equals, true) tools.PrintResource(t, downloadResults[0]) // Replace the object with the same object. // But skip identical segments uploadOpts.SkipIdentical = true uploadResult, err = objects.Upload(client, cName, oName, uploadOpts) th.AssertNoErr(t, err) th.AssertEquals(t, uploadResult.Success, true) th.AssertEquals(t, uploadResult.Status, "skip-identical") tools.PrintResource(t, uploadResult) // Replace the object with the same object. // But only if changed. uploadOpts.SkipIdentical = false uploadOpts.Changed = true uploadResult, err = objects.Upload(client, cName, oName, uploadOpts) th.AssertNoErr(t, err) th.AssertEquals(t, uploadResult.Success, true) th.AssertEquals(t, uploadResult.Status, "skip-changed") tools.PrintResource(t, uploadResult) } func TestObjectFileDLO(t *testing.T) { client, err := clientconfig.NewServiceClient("object-store", nil) th.AssertNoErr(t, err) // Create a file with random content source, err := CreateRandomFile(t, "/tmp") th.AssertNoErr(t, err) defer DeleteTempFile(t, source) // Create a destination file. dest := tools.RandomString("/tmp/test-dest-", 8) defer DeleteTempFile(t, dest) // Create a random object name. oName := tools.RandomString("test-object-", 8) // Create a test container to store the object. cName, err := CreateContainer(t, client) th.AssertNoErr(t, err) defer DeleteContainer(t, client, cName) defer DeleteContainer(t, client, cName+"_segments") // Upload the object uploadOpts := &objects.UploadOpts{ Checksum: true, Path: source, SegmentSize: 62, } uploadResult, err := objects.Upload(client, cName, oName, uploadOpts) defer DeleteObject(t, client, cName, oName) th.AssertNoErr(t, err) th.AssertEquals(t, uploadResult.Success, true) tools.PrintResource(t, uploadResult) // Download the object downloadOpts := &objects.DownloadOpts{ OutFile: dest, } downloadResults, err := objects.Download(client, cName, []string{oName}, downloadOpts) th.AssertNoErr(t, err) th.AssertEquals(t, len(downloadResults), 1) th.AssertEquals(t, downloadResults[0].Success, true) tools.PrintResource(t, downloadResults[0]) equals, err := CompareFiles(t, source, dest) th.AssertNoErr(t, err) th.AssertEquals(t, equals, true) // Replace the object with the same object. uploadResult, err = objects.Upload(client, cName, oName, uploadOpts) th.AssertNoErr(t, err) th.AssertEquals(t, uploadResult.Success, true) tools.PrintResource(t, uploadResult) // Download the object downloadResults, err = objects.Download(client, cName, []string{oName}, downloadOpts) th.AssertNoErr(t, err) th.AssertEquals(t, len(downloadResults), 1) th.AssertEquals(t, downloadResults[0].Success, true) tools.PrintResource(t, downloadResults[0]) equals, err = CompareFiles(t, source, dest) th.AssertNoErr(t, err) th.AssertEquals(t, equals, true) tools.PrintResource(t, downloadResults[0]) // Replace the object with the same object. // But skip identical segments uploadOpts.SkipIdentical = true uploadResult, err = objects.Upload(client, cName, oName, uploadOpts) th.AssertNoErr(t, err) th.AssertEquals(t, uploadResult.Success, true) th.AssertEquals(t, uploadResult.Status, "skip-identical") tools.PrintResource(t, uploadResult) // Replace the object with the same object. // But only if changed. uploadOpts.SkipIdentical = false uploadOpts.Changed = true uploadResult, err = objects.Upload(client, cName, oName, uploadOpts) th.AssertNoErr(t, err) th.AssertEquals(t, uploadResult.Success, true) th.AssertEquals(t, uploadResult.Status, "skip-changed") tools.PrintResource(t, uploadResult) } func TestObjectPseudoDirBasic(t *testing.T) { client, err := clientconfig.NewServiceClient("object-store", nil) th.AssertNoErr(t, err) // Create a test container to store the object. cName, err := CreateContainer(t, client) th.AssertNoErr(t, err) defer DeleteContainer(t, client, cName) // Generate a random object name and random content. oName := tools.RandomString("test-object-", 8) // Create the directory marker uploadOpts := &objects.UploadOpts{ DirMarker: true, } uploadResult, err := objects.Upload(client, cName, oName, uploadOpts) defer DeleteObject(t, client, cName, oName) th.AssertNoErr(t, err) th.AssertEquals(t, uploadResult.Success, true) tools.PrintResource(t, uploadResult) // Get the object obj, err := GetObject(client, cName, oName) th.AssertNoErr(t, err) th.AssertEquals(t, obj.ContentType, "application/directory") } func TestObjectPseudoDirFileStructure(t *testing.T) { client, err := clientconfig.NewServiceClient("object-store", nil) th.AssertNoErr(t, err) // Create a test container to store the object. cName, err := CreateContainer(t, client) th.AssertNoErr(t, err) defer DeleteContainer(t, client, cName) // Create a temporary directory to hold files. parentDir, err := CreateTempDir(t, "/tmp") th.AssertNoErr(t, err) defer DeleteTempDir(t, parentDir) oName := path.Base(parentDir) // Upload the directory uploadOpts := &objects.UploadOpts{ Path: parentDir, } uploadResult, err := objects.Upload(client, cName, oName, uploadOpts) defer DeleteObject(t, client, cName, oName) th.AssertNoErr(t, err) th.AssertEquals(t, uploadResult.Success, true) tools.PrintResource(t, uploadResult) // Create a file with random content source, err := CreateRandomFile(t, parentDir) th.AssertNoErr(t, err) defer DeleteTempFile(t, source) oName = path.Join(oName, path.Base(source)) // Upload the file. uploadOpts.Path = source uploadResult, err = objects.Upload(client, cName, oName, uploadOpts) defer DeleteObject(t, client, cName, oName) th.AssertNoErr(t, err) th.AssertEquals(t, uploadResult.Success, true) tools.PrintResource(t, uploadResult) // Create a nested directory to hold files. nestedDir, err := CreateTempDir(t, parentDir) th.AssertNoErr(t, err) defer DeleteTempDir(t, nestedDir) oName = path.Join(path.Base(parentDir), path.Base(nestedDir)) // Upload the nested directory uploadOpts.Path = nestedDir uploadResult, err = objects.Upload(client, cName, oName, uploadOpts) defer DeleteObject(t, client, cName, oName) th.AssertNoErr(t, err) th.AssertEquals(t, uploadResult.Success, true) tools.PrintResource(t, uploadResult) // Create a file in the nested directory with random content nestedSource, err := CreateRandomFile(t, nestedDir) th.AssertNoErr(t, err) defer DeleteTempFile(t, nestedSource) oName = path.Join(oName, path.Base(nestedSource)) // Upload the file. uploadOpts.Path = nestedSource uploadResult, err = objects.Upload(client, cName, oName, uploadOpts) defer DeleteObject(t, client, cName, oName) th.AssertNoErr(t, err) th.AssertEquals(t, uploadResult.Success, true) tools.PrintResource(t, uploadResult) // Create a temporary directory to download files. downloadDir, err := CreateTempDir(t, "/tmp") th.AssertNoErr(t, err) defer DeleteTempDir(t, downloadDir) // Download the container to downloadDir downloadOpts := &objects.DownloadOpts{ OutDirectory: downloadDir, } downloadResults, err := objects.Download(client, cName, []string{}, downloadOpts) th.AssertNoErr(t, err) // Compare the downloaded content for _, dr := range downloadResults { pseudoDir := dr.PseudoDir stat, err := os.Stat(dr.Path) th.AssertNoErr(t, err) th.AssertEquals(t, stat.IsDir(), pseudoDir) if !pseudoDir { v := path.Join("/tmp", dr.Object) equals, err := CompareFiles(t, v, dr.Path) th.AssertNoErr(t, err) th.AssertEquals(t, equals, true) } } tools.PrintResource(t, downloadResults) } func TestObjectFileHandleUploadDownload(t *testing.T) { client, err := clientconfig.NewServiceClient("object-store", nil) th.AssertNoErr(t, err) // Create a file with random content source, err := CreateRandomFile(t, "/tmp") th.AssertNoErr(t, err) defer DeleteTempFile(t, source) // Create a destination file. dest := tools.RandomString("/tmp/test-dest-", 8) // Create a random object name. oName := tools.RandomString("test-object-", 8) // Create a test container to store the object. cName, err := CreateContainer(t, client) th.AssertNoErr(t, err) defer DeleteContainer(t, client, cName) f, err := os.Open(source) th.AssertNoErr(t, err) // Upload the object uploadOpts := &objects.UploadOpts{ Checksum: true, Content: f, } uploadResult, err := objects.Upload(client, cName, oName, uploadOpts) defer DeleteObject(t, client, cName, oName) th.AssertNoErr(t, err) th.AssertEquals(t, uploadResult.Success, true) tools.PrintResource(t, uploadResult) err = f.Close() th.AssertNoErr(t, err) // Download the object downloadOpts := &objects.DownloadOpts{ OutFile: dest, } downloadResults, err := objects.Download(client, cName, []string{oName}, downloadOpts) th.AssertNoErr(t, err) defer DeleteTempFile(t, dest) th.AssertEquals(t, len(downloadResults), 1) th.AssertEquals(t, downloadResults[0].Success, true) tools.PrintResource(t, downloadResults[0]) equals, err := CompareFiles(t, source, dest) th.AssertNoErr(t, err) th.AssertEquals(t, equals, true) } func TestObjectStreamReaderUploadDownload(t *testing.T) { client, err := clientconfig.NewServiceClient("object-store", nil) th.AssertNoErr(t, err) // Create a stream with content r, err := http.Get("https://google.com") th.AssertNoErr(t, err) // Create a destination file. dest := tools.RandomString("/tmp/test-dest-", 8) // Create a random object name. oName := tools.RandomString("test-object-", 8) // Create a test container to store the object. cName, err := CreateContainer(t, client) th.AssertNoErr(t, err) defer DeleteContainer(t, client, cName) // Upload the object uploadOpts := &objects.UploadOpts{ Checksum: true, Content: r.Body, } uploadResult, err := objects.Upload(client, cName, oName, uploadOpts) defer DeleteObject(t, client, cName, oName) th.AssertNoErr(t, err) th.AssertEquals(t, uploadResult.Success, true) tools.PrintResource(t, uploadResult) th.AssertNoErr(t, err) // Download the object downloadOpts := &objects.DownloadOpts{ OutFile: dest, } downloadResults, err := objects.Download(client, cName, []string{oName}, downloadOpts) th.AssertNoErr(t, err) defer DeleteTempFile(t, dest) th.AssertEquals(t, len(downloadResults), 1) th.AssertEquals(t, downloadResults[0].Success, true) tools.PrintResource(t, downloadResults[0]) } objectstorage.go000066400000000000000000000110061467426574700346460ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/acceptance/openstack/objectstorage/v1package v1 import ( "bytes" "fmt" "io/ioutil" "os" "testing" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/acceptance/tools" "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers" "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects" ) // CompareFiles will compare two files func CompareFiles(t *testing.T, file1, file2 string) (bool, error) { f1, err := os.Open(file1) if err != nil { return false, fmt.Errorf("unable to open %s: %s", file1, err) } defer f1.Close() f2, err := os.Open(file2) if err != nil { return false, fmt.Errorf("unable to open %s: %s", file2, err) } defer f2.Close() contents1, err := ioutil.ReadAll(f1) if err != nil { return false, fmt.Errorf("unable to read %s: %s", file1, err) } contents2, err := ioutil.ReadAll(f2) if err != nil { return false, fmt.Errorf("unable to read %s: %s", file2, err) } equal := bytes.Equal(contents1, contents2) return equal, nil } // CreateContainer will create a container with a random name. func CreateContainer(t *testing.T, client *gophercloud.ServiceClient) (string, error) { cName := tools.RandomString("test-container-", 8) res := containers.Create(client, cName, nil) t.Logf("creating container: %s", cName) return cName, res.Err } // CreateRandomFile will create a file with random content. func CreateRandomFile(t *testing.T, parentDir string) (string, error) { tmpfile, err := CreateTempFile(t, parentDir) if err != nil { return "", fmt.Errorf("unable to create random file: %s", err) } content := tools.RandomString("", 256) tmpfile.Write([]byte(content)) tmpfile.Close() return tmpfile.Name(), nil } // CreateTempDir will create and return a temp directory. func CreateTempDir(t *testing.T, parentDir string) (string, error) { dirName, err := ioutil.TempDir(parentDir, "test-dir-") if err != nil { return "", err } t.Logf("creating tempdir: %s", dirName) return dirName, nil } // CreateTempFile will create and return a temp file. func CreateTempFile(t *testing.T, dir string) (*os.File, error) { fileName := tools.RandomString("test-file-", 8) t.Logf("creating tempfile: %s", fileName) return ioutil.TempFile(dir, fileName) } // DeleteContainer will delete a container. A fatal error will occur if the // container failed to be deleted. This works best when used as a deferred // function. func DeleteContainer(t *testing.T, client *gophercloud.ServiceClient, cName string) { t.Logf("deleting container %s", cName) allPages, err := objects.List(client, cName, nil).AllPages() if err != nil { t.Fatalf("unable to list container %s: %s", cName, err) } allObjects, err := objects.ExtractNames(allPages) if err != nil { t.Fatalf("unable to extract container %s: %s", cName, err) } for _, oName := range allObjects { res := objects.Delete(client, cName, oName, nil) if res.Err != nil { t.Fatalf("unable to delete object: %s/%s: %s", cName, oName, oName) } } res := containers.Delete(client, cName) if res.Err != nil { t.Fatalf("unable to delete container %s: %s", cName, res.Err) } } // DeleteObject will delete an object. A fatal error will occur if the object // failed to be deleted. This works best when used as a deferred function. func DeleteObject(t *testing.T, client *gophercloud.ServiceClient, cName, oName string) { t.Logf("deleting object %s/%s", cName, oName) res := objects.Delete(client, cName, oName, nil) if res.Err != nil { t.Fatalf("unable to delete object %s/%s: %s", cName, oName, res.Err) } } // DeleteTempFile will delete a temporary file. A fatal error will occur if the // file could not be deleted. This works best when used as a deferred function. func DeleteTempFile(t *testing.T, fileName string) { t.Logf("deleting tempfile %s", fileName) if err := os.Remove(fileName); err != nil { t.Fatalf("unable to delete tempfile %s: %s", fileName, err) } } // DeleteTempDir will delete a temporary directory. A fatal error will occur if // the directory could not be deleted. This works best when used as a deferred // function. func DeleteTempDir(t *testing.T, dirName string) { t.Logf("deleting tempdir %s", dirName) if err := os.RemoveAll(dirName); err != nil { t.Fatalf("unable to delete tempdir %s: %s", dirName, err) } } // GetObject is an alias to objects.GetObject so we don't have to import // gophercloud/gophercloud into objects_test.go and make things confusing. func GetObject(client *gophercloud.ServiceClient, cName, oName string) (*objects.GetHeader, error) { return objects.Get(client, cName, oName, nil).Extract() } golang-github-gophercloud-utils-0.0~git20231010.80377ec/client/000077500000000000000000000000001467426574700235655ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/client/client.go000066400000000000000000000252161467426574700254000ustar00rootroot00000000000000package client import ( "bytes" "context" "encoding/json" "fmt" "io" "io/ioutil" "log" "net/http" "sort" "strconv" "strings" "time" "github.com/gophercloud/gophercloud" ) // Logger is an interface representing the Logger struct type Logger interface { Printf(format string, args ...interface{}) } // DefaultLogger is a default struct, which satisfies the Logger interface type DefaultLogger struct{} // Printf is a default Printf method func (DefaultLogger) Printf(format string, args ...interface{}) { log.Printf("[DEBUG] "+format, args...) } // noopLogger is a default noop logger satisfies the Logger interface type noopLogger struct{} // Printf is a default noop method func (noopLogger) Printf(format string, args ...interface{}) {} // RoundTripper satisfies the http.RoundTripper interface and is used to // customize the default http client RoundTripper type RoundTripper struct { // Default http.RoundTripper Rt http.RoundTripper // Additional request headers to be set (not appended) in all client // requests headers *http.Header // A pointer to a map of headers to be masked in logger maskHeaders *map[string]struct{} // A custom function to format and mask JSON requests and responses FormatJSON func([]byte) (string, error) // How many times HTTP connection should be retried until giving up MaxRetries int // If Logger is not nil, then RoundTrip method will debug the JSON // requests and responses Logger Logger } // List of headers that contain sensitive data. var defaultSensitiveHeaders = map[string]struct{}{ "x-auth-token": {}, "x-auth-key": {}, "x-service-token": {}, "x-storage-token": {}, "x-account-meta-temp-url-key": {}, "x-account-meta-temp-url-key-2": {}, "x-container-meta-temp-url-key": {}, "x-container-meta-temp-url-key-2": {}, "set-cookie": {}, "x-subject-token": {}, "authorization": {}, } // GetDefaultSensitiveHeaders returns the default list of headers to be masked func GetDefaultSensitiveHeaders() []string { headers := make([]string, len(defaultSensitiveHeaders)) i := 0 for k := range defaultSensitiveHeaders { headers[i] = k i++ } return headers } // SetSensitiveHeaders sets the list of case insensitive headers to be masked in // debug log func (rt *RoundTripper) SetSensitiveHeaders(headers []string) { newHeaders := make(map[string]struct{}, len(headers)) for _, h := range headers { newHeaders[h] = struct{}{} } // this is concurrency safe rt.maskHeaders = &newHeaders } // SetHeaders sets request headers to be set (not appended) in all client // requests func (rt *RoundTripper) SetHeaders(headers http.Header) { newHeaders := make(http.Header, len(headers)) for k, v := range headers { s := make([]string, len(v)) for i, v := range v { s[i] = v } newHeaders[k] = s } // this is concurrency safe rt.headers = &newHeaders } func (rt *RoundTripper) hideSensitiveHeadersData(headers http.Header) []string { result := make([]string, len(headers)) headerIdx := 0 // this is concurrency safe v := rt.maskHeaders if v == nil { v = &defaultSensitiveHeaders } maskHeaders := *v for header, data := range headers { v := strings.ToLower(header) if _, ok := maskHeaders[v]; ok { result[headerIdx] = fmt.Sprintf("%s: %s", header, "***") } else { result[headerIdx] = fmt.Sprintf("%s: %s", header, strings.Join(data, " ")) } headerIdx++ } return result } // formatHeaders converts standard http.Header type to a string with separated headers. // It will hide data of sensitive headers. func (rt *RoundTripper) formatHeaders(headers http.Header, separator string) string { redactedHeaders := rt.hideSensitiveHeadersData(headers) sort.Strings(redactedHeaders) return strings.Join(redactedHeaders, separator) } // RoundTrip performs a round-trip HTTP request and logs relevant information about it. func (rt *RoundTripper) RoundTrip(request *http.Request) (*http.Response, error) { defer func() { if request.Body != nil { request.Body.Close() } }() // for future reference, this is how to access the Transport struct: //tlsconfig := rt.Rt.(*http.Transport).TLSClientConfig // this is concurrency safe h := rt.headers if h != nil { for k, v := range *h { // Set additional request headers request.Header[k] = v } } var err error if rt.Logger != nil { rt.log().Printf("OpenStack Request URL: %s %s", request.Method, request.URL) rt.log().Printf("OpenStack Request Headers:\n%s", rt.formatHeaders(request.Header, "\n")) if request.Body != nil { request.Body, err = rt.logRequest(request.Body, request.Header.Get("Content-Type")) if err != nil { return nil, err } } } // this is concurrency safe ort := rt.Rt if ort == nil { return nil, fmt.Errorf("Rt RoundTripper is nil, aborting") } response, err := ort.RoundTrip(request) // If the first request didn't return a response, retry up to `max_retries`. retry := 1 for response == nil { if retry > rt.MaxRetries { if rt.Logger != nil { rt.log().Printf("OpenStack connection error, retries exhausted. Aborting") } err = fmt.Errorf("OpenStack connection error, retries exhausted. Aborting. Last error was: %s", err) return nil, err } if rt.Logger != nil { rt.log().Printf("OpenStack connection error, retry number %d: %s", retry, err) } response, err = ort.RoundTrip(request) retry += 1 } if rt.Logger != nil { rt.log().Printf("OpenStack Response Code: %d", response.StatusCode) rt.log().Printf("OpenStack Response Headers:\n%s", rt.formatHeaders(response.Header, "\n")) response.Body, err = rt.logResponse(response.Body, response.Header.Get("Content-Type")) } return response, err } // logRequest will log the HTTP Request details. // If the body is JSON, it will attempt to be pretty-formatted. func (rt *RoundTripper) logRequest(original io.ReadCloser, contentType string) (io.ReadCloser, error) { // Handle request contentType if strings.HasPrefix(contentType, "application/json") || (strings.HasPrefix(contentType, "application/") && strings.HasSuffix(contentType, "-json-patch")) { var bs bytes.Buffer defer original.Close() _, err := io.Copy(&bs, original) if err != nil { return nil, err } debugInfo, err := rt.formatJSON()(bs.Bytes()) if err != nil { rt.log().Printf("%s", err) } rt.log().Printf("OpenStack Request Body: %s", debugInfo) return ioutil.NopCloser(strings.NewReader(bs.String())), nil } rt.log().Printf("Not logging because OpenStack request body isn't JSON") return original, nil } // logResponse will log the HTTP Response details. // If the body is JSON, it will attempt to be pretty-formatted. func (rt *RoundTripper) logResponse(original io.ReadCloser, contentType string) (io.ReadCloser, error) { if strings.HasPrefix(contentType, "application/json") { var bs bytes.Buffer defer original.Close() _, err := io.Copy(&bs, original) if err != nil { return nil, err } debugInfo, err := rt.formatJSON()(bs.Bytes()) if err != nil { rt.log().Printf("%s", err) } if debugInfo != "" { rt.log().Printf("OpenStack Response Body: %s", debugInfo) } return ioutil.NopCloser(strings.NewReader(bs.String())), nil } rt.log().Printf("Not logging because OpenStack response body isn't JSON") return original, nil } func (rt *RoundTripper) formatJSON() func([]byte) (string, error) { // this is concurrency safe f := rt.FormatJSON if f == nil { return FormatJSON } return f } func (rt *RoundTripper) log() Logger { // this is concurrency safe l := rt.Logger if l == nil { // noop is used, when logger pointer has been set to nil return &noopLogger{} } return l } // FormatJSON is a default function to pretty-format a JSON body. // It will also mask known fields which contain sensitive information. func FormatJSON(raw []byte) (string, error) { var rawData interface{} err := json.Unmarshal(raw, &rawData) if err != nil { return string(raw), fmt.Errorf("unable to parse OpenStack JSON: %s", err) } data, ok := rawData.(map[string]interface{}) if !ok { pretty, err := json.MarshalIndent(rawData, "", " ") if err != nil { return string(raw), fmt.Errorf("unable to re-marshal OpenStack JSON: %s", err) } return string(pretty), nil } // Mask known password fields if v, ok := data["auth"].(map[string]interface{}); ok { // v2 auth methods if v, ok := v["passwordCredentials"].(map[string]interface{}); ok { v["password"] = "***" } if v, ok := v["token"].(map[string]interface{}); ok { v["id"] = "***" } // v3 auth methods if v, ok := v["identity"].(map[string]interface{}); ok { if v, ok := v["password"].(map[string]interface{}); ok { if v, ok := v["user"].(map[string]interface{}); ok { v["password"] = "***" } } if v, ok := v["application_credential"].(map[string]interface{}); ok { v["secret"] = "***" } if v, ok := v["token"].(map[string]interface{}); ok { v["id"] = "***" } } } // Mask EC2 access id and body hash if v, ok := data["credentials"].(map[string]interface{}); ok { var access string if s, ok := v["access"]; ok { access, _ = s.(string) v["access"] = "***" } if _, ok := v["body_hash"]; ok { v["body_hash"] = "***" } if v, ok := v["headers"].(map[string]interface{}); ok { if _, ok := v["Authorization"]; ok { if s, ok := v["Authorization"].(string); ok { v["Authorization"] = strings.Replace(s, access, "***", -1) } } } } // Ignore the huge catalog output if v, ok := data["token"].(map[string]interface{}); ok { if _, ok := v["catalog"]; ok { v["catalog"] = "***" } } pretty, err := json.MarshalIndent(data, "", " ") if err != nil { return string(raw), fmt.Errorf("unable to re-marshal OpenStack JSON: %s", err) } return string(pretty), nil } func RetryBackoffFunc(logger Logger) gophercloud.RetryBackoffFunc { return func(ctx context.Context, respErr *gophercloud.ErrUnexpectedResponseCode, e error, retries uint) error { retryAfter := respErr.ResponseHeader.Get("Retry-After") if retryAfter == "" { return e } var sleep time.Duration // Parse delay seconds or HTTP date if v, err := strconv.ParseUint(retryAfter, 10, 32); err == nil { sleep = time.Duration(v) * time.Second } else if v, err := time.Parse(http.TimeFormat, retryAfter); err == nil { sleep = time.Until(v) } else { return e } l := logger if l != nil { l.Printf("Received StatusTooManyRequests response code sleeping for %s", sleep) } if c := ctx; c != nil { select { case <-time.After(sleep): case <-c.Done(): if l != nil { l.Printf("Sleeping aborted: %w", c.Err()) } return e } } else { time.Sleep(sleep) } return nil } } golang-github-gophercloud-utils-0.0~git20231010.80377ec/client/client_test.go000066400000000000000000000007171467426574700264360ustar00rootroot00000000000000package client import ( "net/http" "testing" th "github.com/gophercloud/gophercloud/testhelper" ) func TestFormatHeaders(t *testing.T) { headers := http.Header{ "X-Auth-Token": []string{"token"}, "User-Agent": []string{"Terraform/x.x.x", "Gophercloud/y.y.y"}, } expected := "User-Agent: Terraform/x.x.x Gophercloud/y.y.y\nX-Auth-Token: ***" rt := RoundTripper{} actual := rt.formatHeaders(headers, "\n") th.AssertEquals(t, expected, actual) } golang-github-gophercloud-utils-0.0~git20231010.80377ec/client/doc.go000066400000000000000000000062551467426574700246710ustar00rootroot00000000000000/* Package client provides an ability to create a http.RoundTripper OpenStack client with extended options, including the JSON requests and responses log capabilities. Example usage with the default logger: package example import ( "net/http" "os" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack" "github.com/gophercloud/utils/client" "github.com/gophercloud/utils/openstack/clientconfig" ) func NewComputeV2Client() (*gophercloud.ServiceClient, error) { ao, err := clientconfig.AuthOptions(nil) if err != nil { return nil, err } provider, err := openstack.NewClient(ao.IdentityEndpoint) if err != nil { return nil, err } if os.Getenv("OS_DEBUG") != "" { provider.HTTPClient = http.Client{ Transport: &client.RoundTripper{ Rt: &http.Transport{}, Logger: &client.DefaultLogger{}, }, } } err = openstack.Authenticate(provider, *ao) if err != nil { return nil, err } return openstack.NewComputeV2(provider, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) } Example usage with the custom logger: package example import ( "net/http" "os" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack" "github.com/gophercloud/utils/client" "github.com/gophercloud/utils/openstack/clientconfig" log "github.com/sirupsen/logrus" ) type myLogger struct { Prefix string } func (l myLogger) Printf(format string, args ...interface{}) { log.Debugf("%s [DEBUG] "+format, append([]interface{}{l.Prefix}, args...)...) } func NewComputeV2Client() (*gophercloud.ServiceClient, error) { ao, err := clientconfig.AuthOptions(nil) if err != nil { return nil, err } provider, err := openstack.NewClient(ao.IdentityEndpoint) if err != nil { return nil, err } if os.Getenv("OS_DEBUG") != "" { provider.HTTPClient = http.Client{ Transport: &client.RoundTripper{ Rt: &http.Transport{}, Logger: &myLogger{Prefix: "myApp"}, }, } } err = openstack.Authenticate(provider, *ao) if err != nil { return nil, err } return openstack.NewComputeV2(provider, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) } Example usage with additinal headers: package example import ( "net/http" "os" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack" "github.com/gophercloud/utils/client" "github.com/gophercloud/utils/openstack/clientconfig" ) func NewComputeV2Client() (*gophercloud.ServiceClient, error) { ao, err := clientconfig.AuthOptions(nil) if err != nil { return nil, err } provider, err := openstack.NewClient(ao.IdentityEndpoint) if err != nil { return nil, err } provider.HTTPClient = http.Client{ Transport: &client.RoundTripper{ Rt: &http.Transport{}, }, } provider.HTTPClient.Transport.(*client.RoundTripper).SetHeaders(map[string][]string{"Cache-Control": {"no-cache"}}}) err = openstack.Authenticate(provider, *ao) if err != nil { return nil, err } return openstack.NewComputeV2(provider, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) } */ package client golang-github-gophercloud-utils-0.0~git20231010.80377ec/env/000077500000000000000000000000001467426574700230775ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/env/env.go000066400000000000000000000001751467426574700242210ustar00rootroot00000000000000//go:build !windows // +build !windows package env import ( "os" ) func Getenv(s string) string { return os.Getenv(s) } golang-github-gophercloud-utils-0.0~git20231010.80377ec/env/env_windows.go000066400000000000000000000043001467426574700257650ustar00rootroot00000000000000package env import ( "os" "syscall" "golang.org/x/sys/windows" "golang.org/x/text/encoding/charmap" ) func Getenv(s string) string { var st uint32 env := os.Getenv(s) if windows.GetConsoleMode(windows.Handle(syscall.Stdin), &st) == nil || windows.GetConsoleMode(windows.Handle(syscall.Stdout), &st) == nil || windows.GetConsoleMode(windows.Handle(syscall.Stderr), &st) == nil { // detect windows console, should be skipped in cygwin environment var cm charmap.Charmap switch windows.GetACP() { case 37: cm = *charmap.CodePage037 case 1047: cm = *charmap.CodePage1047 case 1140: cm = *charmap.CodePage1140 case 437: cm = *charmap.CodePage437 case 850: cm = *charmap.CodePage850 case 852: cm = *charmap.CodePage852 case 855: cm = *charmap.CodePage855 case 858: cm = *charmap.CodePage858 case 860: cm = *charmap.CodePage860 case 862: cm = *charmap.CodePage862 case 863: cm = *charmap.CodePage863 case 865: cm = *charmap.CodePage865 case 866: cm = *charmap.CodePage866 case 28591: cm = *charmap.ISO8859_1 case 28592: cm = *charmap.ISO8859_2 case 28593: cm = *charmap.ISO8859_3 case 28594: cm = *charmap.ISO8859_4 case 28595: cm = *charmap.ISO8859_5 case 28596: cm = *charmap.ISO8859_6 case 28597: cm = *charmap.ISO8859_7 case 28598: cm = *charmap.ISO8859_8 case 28599: cm = *charmap.ISO8859_9 case 28600: cm = *charmap.ISO8859_10 case 28603: cm = *charmap.ISO8859_13 case 28604: cm = *charmap.ISO8859_14 case 28605: cm = *charmap.ISO8859_15 case 28606: cm = *charmap.ISO8859_16 case 20866: cm = *charmap.KOI8R case 21866: cm = *charmap.KOI8U case 1250: cm = *charmap.Windows1250 case 1251: cm = *charmap.Windows1251 case 1252: cm = *charmap.Windows1252 case 1253: cm = *charmap.Windows1253 case 1254: cm = *charmap.Windows1254 case 1255: cm = *charmap.Windows1255 case 1256: cm = *charmap.Windows1256 case 1257: cm = *charmap.Windows1257 case 1258: cm = *charmap.Windows1258 case 874: cm = *charmap.Windows874 default: return env } if v, err := cm.NewEncoder().String(env); err == nil { return v } } return env } golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/000077500000000000000000000000001467426574700237215ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/client.go000066400000000000000000000013631467426574700255310ustar00rootroot00000000000000package gnocchi import ( "github.com/gophercloud/gophercloud" ) func initClientOpts(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts, clientType string) (*gophercloud.ServiceClient, error) { sc := new(gophercloud.ServiceClient) eo.ApplyDefaults(clientType) url, err := client.EndpointLocator(eo) if err != nil { return sc, err } sc.ProviderClient = client sc.Endpoint = url sc.Type = clientType return sc, nil } // NewGnocchiV1 creates a ServiceClient that may be used with the v1 Gnocchi package. func NewGnocchiV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { sc, err := initClientOpts(client, eo, "metric") sc.ResourceBase = sc.Endpoint + "v1/" return sc, err } golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/000077500000000000000000000000001467426574700252045ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/000077500000000000000000000000001467426574700255325ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/archivepolicies/000077500000000000000000000000001467426574700307035ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/archivepolicies/doc.go000066400000000000000000000034771467426574700320120ustar00rootroot00000000000000/* Package archivepolicies provides the ability to retrieve archive policies through the Gnocchi API. Example of Listing archive policies allPages, err := archivepolicies.List(gnocchiClient).AllPages() if err != nil { panic(err) } allArchivePolicies, err := archivepolicies.ExtractArchivePolicies(allPages) if err != nil { panic(err) } for _, archivePolicy := range allArchivePolicies { fmt.Printf("%+v\n", archivePolicy) } Example of Getting an archive policy archivePolicyName = "my_policy" archivePolicy, err := archivepolicies.Get(gnocchiClient, archivePolicyName).Extract() if err != nil { panic(err) } Example of Creating an archive policy createOpts := archivepolicies.CreateOpts{ BackWindow: 31, AggregationMethods: []string{ "sum", "mean", "count", }, Definition: []archivepolicies.ArchivePolicyDefinitionOpts{ { Granularity: "1:00:00", TimeSpan: "90 days, 0:00:00", }, { Granularity: "1 day, 0:00:00", TimeSpan: "100 days, 0:00:00", }, }, Name: "test_policy", } archivePolicy, err := archivepolicies.Create(gnocchiClient, createOpts).Extract() if err != nil { panic(err) } Example of Updating an archive policy updateOpts := archivepolicies.UpdateOpts{ Definition: []archivepolicies.ArchivePolicyDefinitionOpts{ { Granularity: "12:00:00", TimeSpan: "30 days, 0:00:00", }, { Granularity: "1 day, 0:00:00", TimeSpan: "90 days, 0:00:00", }, }, } archivePolicy, err := archivepolicies.Update(gnocchiClient, "test_policy", updateOpts).Extract() if err != nil { panic(err) } Example of Deleting a Gnocchi archive policy err := archivepolicies.Delete(gnocchiClient, "test_policy").ExtractErr() if err != nil { panic(err) } */ package archivepolicies requests.go000066400000000000000000000102651467426574700330320ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/archivepoliciespackage archivepolicies import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/pagination" ) // List makes a request against the Gnocchi API to list archive policies. func List(client *gophercloud.ServiceClient) pagination.Pager { return pagination.NewPager(client, listURL(client), func(r pagination.PageResult) pagination.Page { return ArchivePolicyPage{pagination.SinglePageBase(r)} }) } // Get retrieves a specific Gnocchi archive policy based on its name. func Get(c *gophercloud.ServiceClient, archivePolicyName string) (r GetResult) { _, r.Err = c.Get(getURL(c, archivePolicyName), &r.Body, nil) return } // CreateOptsBuilder allows extensions to add additional parameters to the Create request. type CreateOptsBuilder interface { ToArchivePolicyCreateMap() (map[string]interface{}, error) } // CreateOpts specifies parameters of a new Archive Policy. type CreateOpts struct { // AggregationMethods is a list of functions used to aggregate // multiple measures into an aggregate. AggregationMethods []string `json:"aggregation_methods,omitempty"` // BackWindow configures number of coarsest periods to keep. // It allows to process measures that are older // than the last timestamp period boundary. BackWindow int `json:"back_window,omitempty"` // Definition is a list of parameters that configures // archive policy precision and timespan. Definition []ArchivePolicyDefinitionOpts `json:"definition"` // Name is a name of an archive policy. Name string `json:"name"` } // ArchivePolicyDefinitionOpts represents definition of how metrics of new or // updated Archive Policy will be saved with the selected archive policy. // It configures precision and timespan. type ArchivePolicyDefinitionOpts struct { // Granularity is the level of precision that must be kept when aggregating data. Granularity string `json:"granularity"` // Points is a given aggregates or samples in the lifespan of a time series. // Time series is a list of aggregates ordered by time. // It can be omitted to allow Gnocchi server to calculate it automatically. Points *int `json:"points,omitempty"` // TimeSpan is the time period for which a metric keeps its aggregates. TimeSpan string `json:"timespan"` } // ToArchivePolicyCreateMap constructs a request body from CreateOpts. func (opts CreateOpts) ToArchivePolicyCreateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "") } // Create requests the creation of a new Gnocchi archive policy on the server. func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { b, err := opts.ToArchivePolicyCreateMap() if err != nil { r.Err = err return } _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{201}, }) return } // UpdateOptsBuilder allows extensions to add additional parameters to the Update request. type UpdateOptsBuilder interface { ToArchivePolicyUpdateMap() (map[string]interface{}, error) } // UpdateOpts represents options used to update an archive policy. type UpdateOpts struct { // Definition is a list of parameters that configures // archive policy precision and timespan. Definition []ArchivePolicyDefinitionOpts `json:"definition"` } // ToArchivePolicyUpdateMap constructs a request body from UpdateOpts. func (opts UpdateOpts) ToArchivePolicyUpdateMap() (map[string]interface{}, error) { return gophercloud.BuildRequestBody(opts, "") } // Update accepts a UpdateOpts and updates an existing Gnocchi archive policy using the values provided. func Update(client *gophercloud.ServiceClient, archivePolicyName string, opts UpdateOptsBuilder) (r UpdateResult) { b, err := opts.ToArchivePolicyUpdateMap() if err != nil { r.Err = err return } _, r.Err = client.Patch(updateURL(client, archivePolicyName), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) return } // Delete accepts a Gnocchi archive policy by its name. func Delete(c *gophercloud.ServiceClient, archivePolicyName string) (r DeleteResult) { requestOpts := &gophercloud.RequestOpts{ MoreHeaders: map[string]string{ "Accept": "application/json, */*", }, } _, r.Err = c.Delete(deleteURL(c, archivePolicyName), requestOpts) return } golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/archivepolicies/results.go000066400000000000000000000065471467426574700327470ustar00rootroot00000000000000package archivepolicies import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/pagination" ) type commonResult struct { gophercloud.Result } // Extract is a function that accepts a result and extracts a Gnocchi archive policy. func (r commonResult) Extract() (*ArchivePolicy, error) { var s *ArchivePolicy err := r.ExtractInto(&s) return s, err } // GetResult represents the result of a get operation. Call its Extract // method to interpret it as an ArchivePolicy. type GetResult struct { commonResult } // CreateResult represents the result of a create operation. Call its Extract // method to interpret it as a Gnocchi archive policy. type CreateResult struct { commonResult } // UpdateResult represents the result of an update operation. Call its Extract // method to interpret it as a Gnocchi archive policy. type UpdateResult struct { commonResult } // DeleteResult represents the result of a delete operation. Call its // ExtractErr method to determine if the request succeeded or failed. type DeleteResult struct { gophercloud.ErrResult } // ArchivePolicy represents a Gnocchi archive policy. // Archive policy is an aggregate storage policy attached to a metric. // It determines how long aggregates will be kept in a metric and how they will be aggregated. type ArchivePolicy struct { // AggregationMethods is a list of functions used to aggregate // multiple measures into an aggregate. AggregationMethods []string `json:"aggregation_methods"` // BackWindow configures number of coarsest periods to keep. // It allows to process measures that are older // than the last timestamp period boundary. BackWindow int `json:"back_window"` // Definition is a list of parameters that configures // archive policy precision and timespan. Definition []ArchivePolicyDefinition `json:"definition"` // Name is a name of an archive policy. Name string `json:"name"` } // ArchivePolicyDefinition represents definition of how metrics will // be saved with the selected archive policy. // It configures precision and timespan. type ArchivePolicyDefinition struct { // Granularity is the level of precision that must be kept when aggregating data. Granularity string `json:"granularity"` // Points is a given aggregates or samples in the lifespan of a time series. // Time series is a list of aggregates ordered by time. Points int `json:"points"` // TimeSpan is the time period for which a metric keeps its aggregates. TimeSpan string `json:"timespan"` } // ArchivePolicyPage abstracts the raw results of making a List() request against // the Gnocchi API. // // As Gnocchi API may freely alter the response bodies of structures // returned to the client, you may only safely access the data provided through // the ExtractArchivePolicies call. type ArchivePolicyPage struct { pagination.SinglePageBase } // IsEmpty returns true if an ArchivePolicyPage contains no archive policies. func (r ArchivePolicyPage) IsEmpty() (bool, error) { archivePolicies, err := ExtractArchivePolicies(r) return len(archivePolicies) == 0, err } // ExtractArchivePolicies interprets the results of a single page from a List() call, // producing a slice of ArchivePolicy structs. func ExtractArchivePolicies(r pagination.Page) ([]ArchivePolicy, error) { var s []ArchivePolicy err := (r.(ArchivePolicyPage)).ExtractInto(&s) if err != nil { return nil, err } return s, err } golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/archivepolicies/testing/000077500000000000000000000000001467426574700323605ustar00rootroot00000000000000doc.go000066400000000000000000000000561467426574700333760ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/archivepolicies/testing// archivepolicies unit tests package testing fixtures.go000066400000000000000000000113361467426574700345050ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/archivepolicies/testingpackage testing import "github.com/gophercloud/utils/gnocchi/metric/v1/archivepolicies" // ArchivePoliciesListResult represents a raw server response from a server to a list call. const ArchivePoliciesListResult = ` [ { "aggregation_methods": [ "max", "min" ], "back_window": 0, "definition": [ { "granularity": "1:00:00", "points": 2304, "timespan": "96 days, 0:00:00" }, { "granularity": "0:05:00", "points": 9216, "timespan": "32 days, 0:00:00" }, { "granularity": "1 day, 0:00:00", "points": 400, "timespan": "400 days, 0:00:00" } ], "name": "precise" }, { "aggregation_methods": [ "mean", "sum" ], "back_window": 12, "definition": [ { "granularity": "1:00:00", "points": 2160, "timespan": "90 days, 0:00:00" }, { "granularity": "1 day, 0:00:00", "points": 200, "timespan": "200 days, 0:00:00" } ], "name": "not_so_precise" } ] ` // ListArchivePoliciesExpected represents an expected repsonse from a List request. var ListArchivePoliciesExpected = []archivepolicies.ArchivePolicy{ { AggregationMethods: []string{ "max", "min", }, BackWindow: 0, Definition: []archivepolicies.ArchivePolicyDefinition{ { Granularity: "1:00:00", Points: 2304, TimeSpan: "96 days, 0:00:00", }, { Granularity: "0:05:00", Points: 9216, TimeSpan: "32 days, 0:00:00", }, { Granularity: "1 day, 0:00:00", Points: 400, TimeSpan: "400 days, 0:00:00", }, }, Name: "precise", }, { AggregationMethods: []string{ "mean", "sum", }, BackWindow: 12, Definition: []archivepolicies.ArchivePolicyDefinition{ { Granularity: "1:00:00", Points: 2160, TimeSpan: "90 days, 0:00:00", }, { Granularity: "1 day, 0:00:00", Points: 200, TimeSpan: "200 days, 0:00:00", }, }, Name: "not_so_precise", }, } // ArchivePolicyGetResult represents a raw server response from a server to a get request. const ArchivePolicyGetResult = ` { "aggregation_methods": [ "max", "min", "mean" ], "back_window": 128, "definition": [ { "granularity": "1:00:00", "points": 2160, "timespan": "90 days, 0:00:00" }, { "granularity": "1 day, 0:00:00", "points": 100, "timespan": "100 days, 0:00:00" } ], "name": "test_policy" } ` // ArchivePolicyCreateRequest represents a raw create request. const ArchivePolicyCreateRequest = ` { "aggregation_methods": [ "sum", "mean", "count" ], "back_window": 31, "definition": [ { "granularity": "1:00:00", "timespan": "90 days, 0:00:00" }, { "granularity": "1 day, 0:00:00", "timespan": "100 days, 0:00:00" } ], "name": "test_policy" } ` // ArchivePolicyCreateResponse represents a raw server response from a server to a create request. const ArchivePolicyCreateResponse = ` { "aggregation_methods": [ "sum", "mean", "count" ], "back_window": 31, "definition": [ { "granularity": "1:00:00", "points": 2160, "timespan": "90 days, 0:00:00" }, { "granularity": "1 day, 0:00:00", "points": 100, "timespan": "100 days, 0:00:00" } ], "name": "test_policy" } ` // ArchivePolicyUpdateRequest represents a raw update request. const ArchivePolicyUpdateRequest = ` { "definition": [ { "timespan": "30 days, 0:00:00", "granularity": "12:00:00" }, { "timespan": "90 days, 0:00:00", "granularity": "1 day, 0:00:00" } ] } ` // ArchivePolicyUpdateResponse represents a raw server response from a server to an update request. const ArchivePolicyUpdateResponse = ` { "definition": [ { "points": 60, "timespan": "30 days, 0:00:00", "granularity": "12:00:00" }, { "points": 90, "timespan": "90 days, 0:00:00", "granularity": "1 day, 0:00:00" } ], "back_window": 0, "name": "test_policy", "aggregation_methods": [ "max" ] } ` requests_test.go000066400000000000000000000134771467426574700355560ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/archivepolicies/testingpackage testing import ( "fmt" "net/http" "testing" "github.com/gophercloud/gophercloud/pagination" th "github.com/gophercloud/gophercloud/testhelper" "github.com/gophercloud/utils/gnocchi/metric/v1/archivepolicies" fake "github.com/gophercloud/utils/gnocchi/testhelper/client" ) func TestListArchivePolicies(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() th.Mux.HandleFunc("/v1/archive_policy", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) fmt.Fprintf(w, ArchivePoliciesListResult) }) expected := ListArchivePoliciesExpected pages := 0 err := archivepolicies.List(fake.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) { pages++ actual, err := archivepolicies.ExtractArchivePolicies(page) th.AssertNoErr(t, err) if len(actual) != 2 { t.Fatalf("Expected 2 archive policy, got %d", len(actual)) } th.CheckDeepEquals(t, expected, actual) return true, nil }) th.AssertNoErr(t, err) th.CheckEquals(t, 1, pages) } func TestListArchivePoliciesAllPages(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() th.Mux.HandleFunc("/v1/archive_policy", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) fmt.Fprintf(w, ArchivePoliciesListResult) }) allPages, err := archivepolicies.List(fake.ServiceClient()).AllPages() th.AssertNoErr(t, err) _, err = archivepolicies.ExtractArchivePolicies(allPages) th.AssertNoErr(t, err) } func TestGetArchivePolicy(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() th.Mux.HandleFunc("/v1/archive_policy/test_policy", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) fmt.Fprintf(w, ArchivePolicyGetResult) }) s, err := archivepolicies.Get(fake.ServiceClient(), "test_policy").Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, s.AggregationMethods, []string{ "max", "min", "mean", }) th.AssertEquals(t, s.BackWindow, 128) th.AssertDeepEquals(t, s.Definition, []archivepolicies.ArchivePolicyDefinition{ { Granularity: "1:00:00", Points: 2160, TimeSpan: "90 days, 0:00:00", }, { Granularity: "1 day, 0:00:00", Points: 100, TimeSpan: "100 days, 0:00:00", }, }) th.AssertEquals(t, s.Name, "test_policy") } func TestCreate(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() th.Mux.HandleFunc("/v1/archive_policy", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ArchivePolicyCreateRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) fmt.Fprintf(w, ArchivePolicyCreateResponse) }) opts := archivepolicies.CreateOpts{ BackWindow: 31, AggregationMethods: []string{ "sum", "mean", "count", }, Definition: []archivepolicies.ArchivePolicyDefinitionOpts{ { Granularity: "1:00:00", TimeSpan: "90 days, 0:00:00", }, { Granularity: "1 day, 0:00:00", TimeSpan: "100 days, 0:00:00", }, }, Name: "test_policy", } s, err := archivepolicies.Create(fake.ServiceClient(), opts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, s.AggregationMethods, []string{ "sum", "mean", "count", }) th.AssertEquals(t, s.BackWindow, 31) th.AssertDeepEquals(t, s.Definition, []archivepolicies.ArchivePolicyDefinition{ { Granularity: "1:00:00", Points: 2160, TimeSpan: "90 days, 0:00:00", }, { Granularity: "1 day, 0:00:00", Points: 100, TimeSpan: "100 days, 0:00:00", }, }) th.AssertEquals(t, s.Name, "test_policy") } func TestUpdateArchivePolicy(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() th.Mux.HandleFunc("/v1/archive_policy/test_policy", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ArchivePolicyUpdateRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) fmt.Fprintf(w, ArchivePolicyUpdateResponse) }) updateOpts := archivepolicies.UpdateOpts{ Definition: []archivepolicies.ArchivePolicyDefinitionOpts{ { Granularity: "12:00:00", TimeSpan: "30 days, 0:00:00", }, { Granularity: "1 day, 0:00:00", TimeSpan: "90 days, 0:00:00", }, }, } s, err := archivepolicies.Update(fake.ServiceClient(), "test_policy", updateOpts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, s.AggregationMethods, []string{ "max", }) th.AssertEquals(t, s.BackWindow, 0) th.AssertDeepEquals(t, s.Definition, []archivepolicies.ArchivePolicyDefinition{ { Granularity: "12:00:00", Points: 60, TimeSpan: "30 days, 0:00:00", }, { Granularity: "1 day, 0:00:00", Points: 90, TimeSpan: "90 days, 0:00:00", }, }) th.AssertEquals(t, s.Name, "test_policy") } func TestDelete(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() th.Mux.HandleFunc("/v1/archive_policy/test_policy", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusNoContent) }) res := archivepolicies.Delete(fake.ServiceClient(), "test_policy") th.AssertNoErr(t, res.Err) } golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/archivepolicies/urls.go000066400000000000000000000015331467426574700322210ustar00rootroot00000000000000package archivepolicies import "github.com/gophercloud/gophercloud" const resourcePath = "archive_policy" func resourceURL(c *gophercloud.ServiceClient, archivePolicyName string) string { return c.ServiceURL(resourcePath, archivePolicyName) } func rootURL(c *gophercloud.ServiceClient) string { return c.ServiceURL(resourcePath) } func listURL(c *gophercloud.ServiceClient) string { return rootURL(c) } func getURL(c *gophercloud.ServiceClient, archivePolicyName string) string { return resourceURL(c, archivePolicyName) } func createURL(c *gophercloud.ServiceClient) string { return rootURL(c) } func updateURL(c *gophercloud.ServiceClient, archivePolicyName string) string { return resourceURL(c, archivePolicyName) } func deleteURL(c *gophercloud.ServiceClient, archivePolicyName string) string { return resourceURL(c, archivePolicyName) } golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/measures/000077500000000000000000000000001467426574700273565ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/measures/doc.go000066400000000000000000000072651467426574700304640ustar00rootroot00000000000000/* Package measures provides the ability to retrieve measures through the Gnocchi API. Example of Listing measures of a known metric startTime := time.Date(2018, 1, 4, 10, 0, 0, 0, time.UTC) metricID := "9e5a6441-1044-4181-b66e-34e180753040" listOpts := measures.ListOpts{ Resample: "2h", Granularity: "1h", Start: &startTime, } allPages, err := measures.List(gnocchiClient, metricID, listOpts).AllPages() if err != nil { panic(err) } allMeasures, err := measures.ExtractMeasures(allPages) if err != nil { panic(err) } for _, measure := range allMeasures { fmt.Printf("%+v\n", measure) } Example of Creating measures inside a single metric createOpts := measures.CreateOpts{ Measures: []measures.MeasureOpts{ { Timestamp: time.Date(2018, 1, 18, 12, 31, 0, 0, time.UTC), Value: 101.2, }, { Timestamp: time.Date(2018, 1, 18, 14, 32, 0, 0, time.UTC), Value: 102, }, }, } metricID := "9e5a6441-1044-4181-b66e-34e180753040" if err := measures.Create(gnocchiClient, metricID, createOpts).ExtractErr(); err != nil { panic(err) } Example of Creating measures inside different metrics via metric ID references in a single request currentTimestamp := time.Now().UTC() pastHourTimestamp := currentTimestamp.Add(-1 * time.Hour) createOpts := measures.BatchCreateMetricsOpts{ { ID: "777a01d6-4694-49cb-b86a-5ba9fd4e609e", Measures: []measures.MeasureOpts{ { Timestamp: ¤tTimestamp, Value: 200, }, { Timestamp: &pastHourTimestamp, Value: 300, }, }, }, { ID: "6dbc97c5-bfdf-47a2-b184-02e7fa348d21", Measures: []measures.MeasureOpts{ { Timestamp: ¤tTimestamp, Value: 111, }, { Timestamp: &pastHourTimestamp, Value: 222, }, }, }, } if err := measures.BatchCreateMetrics(gnocchiClient, createOpts).ExtractErr(); err != nil { panic(err) } Example of Creating measures inside different metrics via metric names and resource IDs references of that metrics in a single request currentTimestamp := time.Now().UTC() pastHourTimestamp := currentTimestamp.Add(-1 * time.Hour) createOpts := measures.BatchCreateResourcesMetricsOpts{ CreateMetrics: true, BatchResourcesMetrics: []measures.BatchResourcesMetricsOpts{ { ResourceID: "75274f99-faf6-4112-a6d5-2794cb07c789", ResourcesMetrics: []measures.ResourcesMetricsOpts{ { MetricName: "network.incoming.bytes.rate", ArchivePolicyName: "high", Unit: "B/s", Measures: []measures.MeasureOpts{ { Timestamp: ¤tTimestamp, Value: 1562.82, }, { Timestamp: &pastHourTimestamp, Value: 768.1, }, }, }, { MetricName: "network.outgoing.bytes.rate", ArchivePolicyName: "high", Unit: "B/s", Measures: []measures.MeasureOpts{ { Timestamp: ¤tTimestamp, Value: 273, }, { Timestamp: &pastHourTimestamp, Value: 3141.14, }, }, }, }, }, { ResourceID: "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55", ResourcesMetrics: []measures.ResourcesMetricsOpts{ { MetricName: "disk.write.bytes.rate", ArchivePolicyName: "low", Unit: "B/s", Measures: []measures.MeasureOpts{ { Timestamp: ¤tTimestamp, Value: 1237, }, { Timestamp: &pastHourTimestamp, Value: 132.12, }, }, }, }, }, }, } if err := measures.BatchCreateResourcesMetrics(gnocchiClient, createOpts).ExtractErr(); err != nil { panic(err) } */ package measures golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/measures/requests.go000066400000000000000000000326371467426574700315730ustar00rootroot00000000000000package measures import ( "fmt" "net/url" "time" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/pagination" "github.com/gophercloud/utils/gnocchi" ) // ListOptsBuilder allows extensions to add additional parameters to the // List request. type ListOptsBuilder interface { ToMeasureListQuery() (string, error) } // ListOpts allows to provide additional options to the Gnocchi measures List request. type ListOpts struct { // Refresh can be used to force any unprocessed measures to be handled in the Gnocchi // to ensure that List request returns all aggregates. Refresh bool `q:"refresh"` // Start is a start of time time range for the measures. Start *time.Time // Stop is a stop of time time range for the measures. Stop *time.Time // Aggregation is a needed aggregation method for returned measures. // Gnocchi returns "mean" by default. Aggregation string `q:"aggregation"` // Granularity is a needed time between two series of measures to retreive. // Gnocchi will response with all granularities for available measures by default. Granularity string `q:"granularity"` // Resample allows to select different granularity instead of those that were defined in the // archive policy. Resample string `q:"resample"` } // ToMeasureListQuery formats a ListOpts into a query string. func (opts ListOpts) ToMeasureListQuery() (string, error) { q, err := gophercloud.BuildQueryString(opts) params := q.Query() if opts.Start != nil { params.Add("start", opts.Start.Format(gnocchi.RFC3339NanoNoTimezone)) } if opts.Stop != nil { params.Add("stop", opts.Stop.Format(gnocchi.RFC3339NanoNoTimezone)) } q = &url.URL{RawQuery: params.Encode()} return q.String(), err } // List returns a Pager which allows you to iterate over a collection of // measures. // It accepts a ListOpts struct, which allows you to provide options to a Gnocchi measures List request. func List(c *gophercloud.ServiceClient, metricID string, opts ListOptsBuilder) pagination.Pager { url := listURL(c, metricID) if opts != nil { query, err := opts.ToMeasureListQuery() if err != nil { return pagination.Pager{Err: err} } url += query } return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { return MeasurePage{pagination.SinglePageBase(r)} }) } // CreateOptsBuilder is needed to add measures to the Create request. type CreateOptsBuilder interface { ToMeasureCreateMap() (map[string]interface{}, error) } // MeasureOpts represents options of a single measure that can be created in the Gnocchi. type MeasureOpts struct { // Timestamp represents a measure creation timestamp. Timestamp *time.Time `json:"-" required:"true"` // Value represents a measure data value. Value float64 `json:"value"` } // ToMap is a helper function to convert individual MeasureOpts structure into a sub-map. func (opts MeasureOpts) ToMap() (map[string]interface{}, error) { b, err := gophercloud.BuildRequestBody(opts, "") if err != nil { return nil, err } if opts.Timestamp != nil { b["timestamp"] = opts.Timestamp.Format(gnocchi.RFC3339NanoNoTimezone) } return b, nil } // CreateOpts specifies a parameters for creating measures for a single metric. type CreateOpts struct { // Measures is a set of measures for a single metric that needs to be created. Measures []MeasureOpts } // ToMeasureCreateMap constructs a request body from CreateOpts. func (opts CreateOpts) ToMeasureCreateMap() (map[string]interface{}, error) { measures := make([]map[string]interface{}, len(opts.Measures)) for i, m := range opts.Measures { measureMap, err := m.ToMap() if err != nil { return nil, err } measures[i] = measureMap } return map[string]interface{}{"measures": measures}, nil } // Create requests the creation of a new measures in the single Gnocchi metric. func Create(client *gophercloud.ServiceClient, metricID string, opts CreateOptsBuilder) (r CreateResult) { b, err := opts.ToMeasureCreateMap() if err != nil { r.Err = err return } _, r.Err = client.Post(createURL(client, metricID), b["measures"], &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{202}, MoreHeaders: map[string]string{ "Accept": "application/json, */*", }, }) return } // BatchCreateMetricsOptsBuilder is needed to add measures to the BatchCreateMetrics request. type BatchCreateMetricsOptsBuilder interface { ToMeasuresBatchCreateMetricsMap() (map[string]interface{}, error) } // BatchCreateMetricsOpts specifies a parameters for creating measures for different metrics in a single request. type BatchCreateMetricsOpts []MetricOpts // MetricOpts represents measures of a single metric of the BatchCreateMetrics request. type MetricOpts struct { // ID uniquely identifies the Gnocchi metric. ID string // Measures is a set of measures for a single metric that needs to be created. Measures []MeasureOpts } // ToMap is a helper function to convert individual MetricOpts structure into a sub-map. func (opts MetricOpts) ToMap() (map[string]interface{}, error) { // Check provided MetricOpts fields. if opts.ID == "" { errMsg := "missing input for the MetricOpts 'ID' argument" return nil, fmt.Errorf(errMsg) } if opts.Measures == nil { errMsg := "missing input for the MetricOpts 'Measures' argument" return nil, fmt.Errorf(errMsg) } // measures is a slice of measures maps. measures := make([]map[string]interface{}, len(opts.Measures)) // metricOpts is an internal map representation of the MetricOpts struct. metricOpts := make(map[string]interface{}) for i, measure := range opts.Measures { measureMap, err := measure.ToMap() if err != nil { return nil, err } measures[i] = measureMap } metricOpts[opts.ID] = measures return metricOpts, nil } // ToMeasuresBatchCreateMetricsMap constructs a request body from BatchCreateMetricsOpts. func (opts BatchCreateMetricsOpts) ToMeasuresBatchCreateMetricsMap() (map[string]interface{}, error) { // batchCreateMetricsOpts is an internal representation of the BatchCreateMetricsOpts struct. batchCreateMetricsOpts := make(map[string]interface{}) for _, metricOpts := range opts { metricOptsMap, err := metricOpts.ToMap() if err != nil { return nil, err } for k, v := range metricOptsMap { batchCreateMetricsOpts[k] = v } } return map[string]interface{}{"batchCreateMetrics": batchCreateMetricsOpts}, nil } // BatchCreateMetrics requests the creation of a new measures for different metrics. func BatchCreateMetrics(client *gophercloud.ServiceClient, opts BatchCreateMetricsOpts) (r BatchCreateMetricsResult) { b, err := opts.ToMeasuresBatchCreateMetricsMap() if err != nil { r.Err = err return } _, r.Err = client.Post(batchCreateMetricsURL(client), b["batchCreateMetrics"], &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{202}, MoreHeaders: map[string]string{ "Accept": "application/json, */*", }, }) return } // BatchCreateResourcesMetricsOptsBuilder is needed to add measures to the BatchCreateResourcesMetrics request. type BatchCreateResourcesMetricsOptsBuilder interface { // ToMeasuresBatchCreateResourcesMetricsMap builds a request body. ToMeasuresBatchCreateResourcesMetricsMap() (map[string]interface{}, error) // ToMeasuresBatchCreateResourcesMetricsQuery builds a query string. ToMeasuresBatchCreateResourcesMetricsQuery() (string, error) } // BatchCreateResourcesMetricsOpts specifies a parameters for creating measures inside metrics via resource IDs and metric names. type BatchCreateResourcesMetricsOpts struct { // CreateMetrics allows request to create metrics that don't exist yet. CreateMetrics bool `q:"create_metrics"` // BatchResourcesMetrics is a map of resource IDs, metrics names and corresponding measures that needs to be created. BatchResourcesMetrics []BatchResourcesMetricsOpts } // BatchResourcesMetricsOpts represents parameters of a single resource of the BatchCreateResourcesMetrics request. type BatchResourcesMetricsOpts struct { // ResourceID uniquely identifies the Gnocchi resource. ResourceID string // ResourcesMetrics specifies metrics whose measures will be updated. ResourcesMetrics []ResourcesMetricsOpts } // ToMap is a helper function to convert individual BatchResourcesMetricsOpts structure into a sub-map. func (opts BatchResourcesMetricsOpts) ToMap() (map[string]interface{}, error) { // Check provided BatchResourcesMetricsOpts fields. if opts.ResourceID == "" { errMsg := "missing input for the BatchResourcesMetricsOpts 'ResourceID' argument" return nil, fmt.Errorf(errMsg) } if opts.ResourcesMetrics == nil { errMsg := "missing input for the BatchResourcesMetricsOpts 'ResourcesMetrics' argument" return nil, fmt.Errorf(errMsg) } // batchResourcesMetricsOpts is an internal map representation of the BatchResourcesMetricsOpts struct. batchResourcesMetricsOpts := make(map[string]interface{}) // resourcesMetricsMaps is a temporary slice that contains different metrics for a single resource. resourcesMetricsMaps := make([]map[string]interface{}, len(opts.ResourcesMetrics)) // resourcesMetricsOptsMap is a temporary map that contains join of different maps from resourcesMetricsMaps slice. resourcesMetricsOptsMap := make(map[string]interface{}) // Populate the temporary resourcesMetricsMaps slice. for i, resourcesMetrics := range opts.ResourcesMetrics { resourcesMetricsMap, err := resourcesMetrics.ToMap() if err != nil { return nil, err } resourcesMetricsMaps[i] = resourcesMetricsMap } // Populate the temporary resourcesMetricsOptsMap map. for _, resourcesMetricsMap := range resourcesMetricsMaps { for k, v := range resourcesMetricsMap { resourcesMetricsOptsMap[k] = v } } // Populate the final map batchResourcesMetricsOpts. batchResourcesMetricsOpts[opts.ResourceID] = resourcesMetricsOptsMap return batchResourcesMetricsOpts, nil } // ResourcesMetricsOpts represents measures of a single metric of the resource from BatchResourcesMetricsOpts. type ResourcesMetricsOpts struct { // MetricName is a human-readable name for the Gnocchi metric. MetricName string // ArchivePolicyName is a name of the Gnocchi archive policy that describes // the aggregate storage policy of a metric. ArchivePolicyName string // Unit is a unit of measurement for measures of that Gnocchi metric. Unit string // Measures is a set of measures for a single metric that needs to be created. Measures []MeasureOpts } // ToMap is a helper function to convert individual ResourcesMetricsOpts structure into a sub-map. func (opts ResourcesMetricsOpts) ToMap() (map[string]interface{}, error) { // Check provided ResourcesMetricsOpts fields. if opts.MetricName == "" { errMsg := "missing input for the ResourcesMetricsOpts 'MetricName' argument" return nil, fmt.Errorf(errMsg) } if opts.Measures == nil { errMsg := "missing input for the ResourcesMetricsOpts 'Measures' argument" return nil, fmt.Errorf(errMsg) } // measures is a slice of measures maps. measures := make([]map[string]interface{}, len(opts.Measures)) // resourcesMetricsOpts is an internal map representation of the ResourcesMetricsOpts struct. resourcesMetricsOpts := make(map[string]interface{}) // metricOpts is an internal nested map for each metric in the resourcesMetricsOpts. metricOpts := make(map[string]interface{}) // Populate metricOpts with values from provided opts. if opts.ArchivePolicyName != "" { metricOpts["archive_policy_name"] = opts.ArchivePolicyName } if opts.Unit != "" { metricOpts["unit"] = opts.Unit } for i, measure := range opts.Measures { measureMap, err := measure.ToMap() if err != nil { return nil, err } measures[i] = measureMap } metricOpts["measures"] = measures resourcesMetricsOpts[opts.MetricName] = metricOpts return resourcesMetricsOpts, nil } // ToMeasuresBatchCreateResourcesMetricsMap constructs a request body from the BatchCreateResourcesMetricsOpts. func (opts BatchCreateResourcesMetricsOpts) ToMeasuresBatchCreateResourcesMetricsMap() (map[string]interface{}, error) { // batchCreateResourcesMetricsOpts is an internal representation of the // BatchCreateResourcesMetricsOpts's BatchResourcesMetrics field. batchCreateResourcesMetricsOpts := make(map[string]interface{}) for _, resourceMetricsOpts := range opts.BatchResourcesMetrics { resourceMetricsOptsMap, err := resourceMetricsOpts.ToMap() if err != nil { return nil, err } for k, v := range resourceMetricsOptsMap { batchCreateResourcesMetricsOpts[k] = v } } return map[string]interface{}{"batchCreateResourcesMetrics": batchCreateResourcesMetricsOpts}, nil } // ToMeasuresBatchCreateResourcesMetricsQuery formats the BatchCreateResourcesMetricsOpts into a query string. func (opts BatchCreateResourcesMetricsOpts) ToMeasuresBatchCreateResourcesMetricsQuery() (string, error) { q, err := gophercloud.BuildQueryString(opts) return q.String(), err } // BatchCreateResourcesMetrics requests the creation of new measures inside metrics via resource IDs and metric names. func BatchCreateResourcesMetrics(client *gophercloud.ServiceClient, opts BatchCreateResourcesMetricsOptsBuilder) (r BatchCreateResourcesMetricsResult) { url := batchCreateResourcesMetricsURL(client) if opts != nil { query, err := opts.ToMeasuresBatchCreateResourcesMetricsQuery() if err != nil { r.Err = err return } url += query } b, err := opts.ToMeasuresBatchCreateResourcesMetricsMap() if err != nil { r.Err = err return } _, r.Err = client.Post(url, b["batchCreateResourcesMetrics"], &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{202}, MoreHeaders: map[string]string{ "Accept": "application/json, */*", }, }) return } golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/measures/results.go000066400000000000000000000066251467426574700314170ustar00rootroot00000000000000package measures import ( "encoding/json" "fmt" "time" "github.com/gophercloud/utils/gnocchi" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/pagination" ) // CreateResult represents the result of a create operation. Call its // ExtractErr method to determine if the request succeeded or failed. type CreateResult struct { gophercloud.ErrResult } // BatchCreateMetricsResult represents the result of a batch create metrics operation. Call its // ExtractErr method to determine if the request succeeded or failed. type BatchCreateMetricsResult struct { gophercloud.ErrResult } // BatchCreateResourcesMetricsResult represents the result of a batch create via resource IDs operation. // Call its ExtractErr method to determine if the request succeeded or failed. type BatchCreateResourcesMetricsResult struct { gophercloud.ErrResult } // Measure is an datapoint thats is composed with a timestamp and a value. type Measure struct { // Timestamp represents a timestamp of when measure was pushed into the Gnocchi. Timestamp time.Time `json:"-"` // Granularity is a level of precision that is kept when aggregating data. Granularity float64 `json:"-"` // Value represents a value of data that was pushed into the Gnocchi. Value float64 `json:"-"` } /* UnmarshalJSON helps to unmarshal response from reading Gnocchi measures. Gnocchi APIv1 returns measures in a such format: [ [ "2017-01-08T10:00:00+00:00", 300.0, 146.0 ], [ "2017-01-08T10:05:00+00:00", 300.0, 58.0 ] ] Helper unmarshals every nested array into the Measure type. */ func (r *Measure) UnmarshalJSON(b []byte) error { var measuresSlice []interface{} err := json.Unmarshal(b, &measuresSlice) if err != nil { return err } // We need to check that a measure contains all needed data. if len(measuresSlice) != 3 { errMsg := fmt.Sprintf("got an invalid measure: %v", measuresSlice) return fmt.Errorf(errMsg) } type tmp Measure var s struct { tmp } *r = Measure(s.tmp) // Populate a measure's timestamp. var timeStamp string var ok bool if timeStamp, ok = measuresSlice[0].(string); !ok { errMsg := fmt.Sprintf("got an invalid timestamp of a measure %v: %v", measuresSlice, measuresSlice[0]) return fmt.Errorf(errMsg) } r.Timestamp, err = time.Parse(gnocchi.RFC3339NanoTimezone, timeStamp) if err != nil { return err } // Populate a measure's granularity. if r.Granularity, ok = measuresSlice[1].(float64); !ok { errMsg := fmt.Sprintf("got an invalid granularity of a measure %v: %v", measuresSlice, measuresSlice[1]) return fmt.Errorf(errMsg) } // Populate a measure's value. if r.Value = measuresSlice[2].(float64); !ok { errMsg := fmt.Sprintf("got an invalid value of a measure %v: %v", measuresSlice, measuresSlice[2]) return fmt.Errorf(errMsg) } return nil } // MeasurePage is the page returned by a pager when traversing over a collection // of measures. type MeasurePage struct { pagination.SinglePageBase } // IsEmpty checks whether a MeasurePage struct is empty. func (r MeasurePage) IsEmpty() (bool, error) { is, err := ExtractMeasures(r) return len(is) == 0, err } // ExtractMeasures interprets the results of a single page from a List() call, // producing a slice of Measures structs. func ExtractMeasures(r pagination.Page) ([]Measure, error) { var s []Measure err := (r.(MeasurePage)).ExtractInto(&s) if err != nil { return nil, err } return s, err } golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/measures/testing/000077500000000000000000000000001467426574700310335ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/measures/testing/doc.go000066400000000000000000000000471467426574700321300ustar00rootroot00000000000000// measures unit tests package testing fixtures.go000066400000000000000000000063551467426574700331650ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/measures/testingpackage testing import ( "time" "github.com/gophercloud/utils/gnocchi/metric/v1/measures" ) // MeasuresListResult represents a raw server response from a server to a List call. const MeasuresListResult = ` [ [ "2018-01-10T12:00:00+00:00", 3600.0, 15.0 ], [ "2018-01-10T13:00:00+00:00", 3600.0, 10.0 ], [ "2018-01-10T14:00:00+00:00", 3600.0, 20.0 ] ] ` // ListMeasuresExpected represents an expected repsonse from a List request. var ListMeasuresExpected = []measures.Measure{ { Timestamp: time.Date(2018, 1, 10, 12, 0, 0, 0, time.UTC), Granularity: 3600.0, Value: 15.0, }, { Timestamp: time.Date(2018, 1, 10, 13, 0, 0, 0, time.UTC), Granularity: 3600.0, Value: 10.0, }, { Timestamp: time.Date(2018, 1, 10, 14, 0, 0, 0, time.UTC), Granularity: 3600.0, Value: 20.0, }, } // MeasuresCreateRequest represents a request to create measures for a single metric. const MeasuresCreateRequest = ` [ { "timestamp": "2018-01-18T12:31:00", "value": 101.2 }, { "timestamp": "2018-01-18T14:32:00", "value": 102 } ] ` // MeasuresBatchCreateMetricsRequest represents a request to create measures for a single metric. const MeasuresBatchCreateMetricsRequest = ` { "777a01d6-4694-49cb-b86a-5ba9fd4e609e": [ { "timestamp": "2018-01-10T01:00:00", "value": 200 }, { "timestamp": "2018-01-10T02:45:00", "value": 300 } ], "6dbc97c5-bfdf-47a2-b184-02e7fa348d21": [ { "timestamp": "2018-01-10T01:00:00", "value": 111 }, { "timestamp": "2018-01-10T02:45:00", "value": 222 } ] } ` // MeasuresBatchCreateResourcesMetricsRequest represents a request to create measures for a single metric. const MeasuresBatchCreateResourcesMetricsRequest = ` { "75274f99-faf6-4112-a6d5-2794cb07c789": { "network.incoming.bytes.rate": { "archive_policy_name": "high", "unit": "B/s", "measures": [ { "timestamp": "2018-01-20T12:30:00", "value": 1562.82 }, { "timestamp": "2018-01-20T13:15:00", "value": 768.1 } ] }, "network.outgoing.bytes.rate": { "archive_policy_name": "high", "unit": "B/s", "measures": [ { "timestamp": "2018-01-20T12:30:00", "value": 273 }, { "timestamp": "2018-01-20T13:15:00", "value": 3141.14 } ] } }, "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55": { "disk.write.bytes.rate": { "archive_policy_name": "low", "unit": "B/s", "measures": [ { "timestamp": "2018-01-20T12:30:00", "value": 1237 }, { "timestamp": "2018-01-20T13:15:00", "value": 132.12 } ] } } } ` requests_test.go000066400000000000000000000134551467426574700342250ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/measures/testingpackage testing import ( "fmt" "net/http" "testing" "time" "github.com/gophercloud/gophercloud/pagination" th "github.com/gophercloud/gophercloud/testhelper" "github.com/gophercloud/utils/gnocchi/metric/v1/measures" fake "github.com/gophercloud/utils/gnocchi/testhelper/client" ) func TestListMeasures(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() th.Mux.HandleFunc("/v1/metric/9e5a6441-1044-4181-b66e-34e180753040/measures", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) fmt.Fprintf(w, MeasuresListResult) }) metricID := "9e5a6441-1044-4181-b66e-34e180753040" startTime := time.Date(2018, 1, 10, 12, 0, 0, 0, time.UTC) stopTime := time.Date(2018, 1, 10, 14, 0, 5, 0, time.UTC) opts := measures.ListOpts{ Start: &startTime, Stop: &stopTime, Granularity: "1h", } expected := ListMeasuresExpected pages := 0 err := measures.List(fake.ServiceClient(), metricID, opts).EachPage(func(page pagination.Page) (bool, error) { pages++ actual, err := measures.ExtractMeasures(page) th.AssertNoErr(t, err) if len(actual) != 3 { t.Fatalf("Expected 2 measures, got %d", len(actual)) } th.CheckDeepEquals(t, expected, actual) return true, nil }) th.AssertNoErr(t, err) th.CheckEquals(t, 1, pages) } func TestCreateMeasures(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() th.Mux.HandleFunc("/v1/metric/9e5a6441-1044-4181-b66e-34e180753040/measures", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json, */*") th.TestJSONRequest(t, r, MeasuresCreateRequest) w.WriteHeader(http.StatusAccepted) fmt.Fprintf(w, `{}`) }) firstMeasureTimestamp := time.Date(2018, 1, 18, 12, 31, 0, 0, time.UTC) secondMeasureTimestamp := time.Date(2018, 1, 18, 14, 32, 0, 0, time.UTC) createOpts := measures.CreateOpts{ Measures: []measures.MeasureOpts{ { Timestamp: &firstMeasureTimestamp, Value: 101.2, }, { Timestamp: &secondMeasureTimestamp, Value: 102, }, }, } res := measures.Create(fake.ServiceClient(), "9e5a6441-1044-4181-b66e-34e180753040", createOpts) th.AssertNoErr(t, res.Err) } func TestBatchCreateMetrics(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() th.Mux.HandleFunc("/v1/batch/metrics/measures", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json, */*") th.TestJSONRequest(t, r, MeasuresBatchCreateMetricsRequest) w.WriteHeader(http.StatusAccepted) fmt.Fprintf(w, `{}`) }) firstTimestamp := time.Date(2018, 1, 10, 01, 00, 0, 0, time.UTC) secondTimestamp := time.Date(2018, 1, 10, 02, 45, 0, 0, time.UTC) createOpts := measures.BatchCreateMetricsOpts{ { ID: "777a01d6-4694-49cb-b86a-5ba9fd4e609e", Measures: []measures.MeasureOpts{ { Timestamp: &firstTimestamp, Value: 200, }, { Timestamp: &secondTimestamp, Value: 300, }, }, }, { ID: "6dbc97c5-bfdf-47a2-b184-02e7fa348d21", Measures: []measures.MeasureOpts{ { Timestamp: &firstTimestamp, Value: 111, }, { Timestamp: &secondTimestamp, Value: 222, }, }, }, } res := measures.BatchCreateMetrics(fake.ServiceClient(), createOpts) th.AssertNoErr(t, res.Err) } func TestBatchCreateResourcesMetrics(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() th.Mux.HandleFunc("/v1/batch/resources/metrics/measures", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json, */*") th.TestJSONRequest(t, r, MeasuresBatchCreateResourcesMetricsRequest) w.WriteHeader(http.StatusAccepted) fmt.Fprintf(w, `{}`) }) firstTimestamp := time.Date(2018, 1, 20, 12, 30, 0, 0, time.UTC) secondTimestamp := time.Date(2018, 1, 20, 13, 15, 0, 0, time.UTC) createOpts := measures.BatchCreateResourcesMetricsOpts{ CreateMetrics: true, BatchResourcesMetrics: []measures.BatchResourcesMetricsOpts{ { ResourceID: "75274f99-faf6-4112-a6d5-2794cb07c789", ResourcesMetrics: []measures.ResourcesMetricsOpts{ { MetricName: "network.incoming.bytes.rate", ArchivePolicyName: "high", Unit: "B/s", Measures: []measures.MeasureOpts{ { Timestamp: &firstTimestamp, Value: 1562.82, }, { Timestamp: &secondTimestamp, Value: 768.1, }, }, }, { MetricName: "network.outgoing.bytes.rate", ArchivePolicyName: "high", Unit: "B/s", Measures: []measures.MeasureOpts{ { Timestamp: &firstTimestamp, Value: 273, }, { Timestamp: &secondTimestamp, Value: 3141.14, }, }, }, }, }, { ResourceID: "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55", ResourcesMetrics: []measures.ResourcesMetricsOpts{ { MetricName: "disk.write.bytes.rate", ArchivePolicyName: "low", Unit: "B/s", Measures: []measures.MeasureOpts{ { Timestamp: &firstTimestamp, Value: 1237, }, { Timestamp: &secondTimestamp, Value: 132.12, }, }, }, }, }, }, } res := measures.BatchCreateResourcesMetrics(fake.ServiceClient(), createOpts) th.AssertNoErr(t, res.Err) } golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/measures/urls.go000066400000000000000000000015141467426574700306730ustar00rootroot00000000000000package measures import "github.com/gophercloud/gophercloud" const ( resourcePath = "metric" batchCreateMetricsPath = "batch/metrics" batchCreateResourcesMetricsPath = "batch/resources/metrics" ) func resourceURL(c *gophercloud.ServiceClient, metricID string) string { return c.ServiceURL(resourcePath, metricID, "measures") } func listURL(c *gophercloud.ServiceClient, metricID string) string { return resourceURL(c, metricID) } func createURL(c *gophercloud.ServiceClient, metricID string) string { return resourceURL(c, metricID) } func batchCreateMetricsURL(c *gophercloud.ServiceClient) string { return c.ServiceURL(batchCreateMetricsPath, "measures") } func batchCreateResourcesMetricsURL(c *gophercloud.ServiceClient) string { return c.ServiceURL(batchCreateResourcesMetricsPath, "measures") } golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/metrics/000077500000000000000000000000001467426574700272005ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/metrics/doc.go000066400000000000000000000027201467426574700302750ustar00rootroot00000000000000/* Package metrics provides the ability to retrieve metrics through the Gnocchi API. Example of Listing metrics listOpts := metrics.ListOpts{ Limit: 25, } allPages, err := metrics.List(gnocchiClient, listOpts).AllPages() if err != nil { panic(err) } allMetrics, err := metrics.ExtractMetrics(allPages) if err != nil { panic(err) } for _, metric := range allMetrics { fmt.Printf("%+v\n", metric) } Example of Getting a metric metricID = "9e5a6441-1044-4181-b66e-34e180753040" metric, err := metrics.Get(gnocchiClient, metricID).Extract() if err != nil { panic(err) } Example of Creating a metric and link it to an existing archive policy createOpts := metrics.CreateOpts{ ArchivePolicyName: "low", Name: "network.incoming.packets.rate", Unit: "packet/s", } metric, err := metrics.Create(gnocchiClient, createOpts).Extract() if err != nil { panic(err) } Example of Creating a metric without an archive policy, assuming that Gnocchi has the needed archive policy rule and can assign the policy automatically createOpts := metrics.CreateOpts{ ResourceID: "1f3a0724-1807-4bd1-81f9-ee18c8ff6ccc", Name: "memory.usage", Unit: "MB", } metric, err := metrics.Create(gnocchiClient, createOpts).Extract() if err != nil { panic(err) } Example of Deleting a Gnocchi metric metricID := "01b2953e-de74-448a-a305-c84440697933" err := metrics.Delete(gnocchiClient, metricID).ExtractErr() if err != nil { panic(err) } */ package metrics golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/metrics/requests.go000066400000000000000000000100351467426574700314010ustar00rootroot00000000000000package metrics import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/pagination" ) // ListOptsBuilder allows extensions to add additional parameters to the // List request. type ListOptsBuilder interface { ToMetricListQuery() (string, error) } // ListOpts allows the limiting and sorting of paginated collections through // the Gnocchi API. type ListOpts struct { // Limit allows to limits count of metrics in the response. Limit int `q:"limit"` // Marker is used for pagination. Marker string `q:"marker"` // SortKey allows to sort metrics in the response by key. SortKey string `q:"sort_key"` // SortDir allows to set the direction of sorting. // Can be `asc` or `desc`. SortDir string `q:"sort_dir"` // Creator shows who created the metric. // Usually it contains concatenated string with values from // "created_by_user_id" and "created_by_project_id" fields. Creator string `json:"creator"` // ProjectID is the Identity project of the metric. ProjectID string `json:"project_id"` // UserID is the Identity user of the metric. UserID string `json:"user_id"` } // ToMetricListQuery formats a ListOpts into a query string. func (opts ListOpts) ToMetricListQuery() (string, error) { q, err := gophercloud.BuildQueryString(opts) return q.String(), err } // List returns a Pager which allows you to iterate over a collection of // metrics. It accepts a ListOpts struct, which allows you to limit and sort // the returned collection for a greater efficiency. func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { url := listURL(c) if opts != nil { query, err := opts.ToMetricListQuery() if err != nil { return pagination.Pager{Err: err} } url += query } pager := pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { p := MetricPage{pagination.MarkerPageBase{PageResult: r}} p.MarkerPageBase.Owner = p return p }) return pager } // Get retrieves a specific Gnocchi metric based on its id. func Get(c *gophercloud.ServiceClient, metricID string) (r GetResult) { _, r.Err = c.Get(getURL(c, metricID), &r.Body, nil) return } // CreateOptsBuilder allows to add additional parameters to the // Create request. type CreateOptsBuilder interface { ToMetricCreateMap() (map[string]interface{}, error) } // CreateOpts specifies parameters of a new Gnocchi metric. type CreateOpts struct { // ArchivePolicyName is a name of the Gnocchi archive policy that describes // the aggregate storage policy of a metric. // You can omit it in the request if your Gnocchi installation has the needed // archive policy rule to assign an archive policy by a metric's name. ArchivePolicyName string `json:"archive_policy_name,omitempty"` // Name is a human-readable name for the Gnocchi metric. // You must provide it if you are also providing a ResourceID in the request. Name string `json:"name,omitempty"` // ResourceID identifies the associated Gnocchi resource of the metric. ResourceID string `json:"resource_id,omitempty"` // Unit is a unit of measurement for measures of that Gnocchi metric. Unit string `json:"unit,omitempty"` } // ToMetricCreateMap constructs a request body from CreateOpts. func (opts CreateOpts) ToMetricCreateMap() (map[string]interface{}, error) { b, err := gophercloud.BuildRequestBody(opts, "") if err != nil { return nil, err } return b, nil } // Create requests the creation of a new Gnocchi metric on the server. func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { b, err := opts.ToMetricCreateMap() if err != nil { r.Err = err return } _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{201}, }) return } // Delete accepts a unique ID and deletes the Gnocchi metric associated with it. func Delete(c *gophercloud.ServiceClient, metricID string) (r DeleteResult) { requestOpts := &gophercloud.RequestOpts{ MoreHeaders: map[string]string{ "Accept": "application/json, */*", }, } _, r.Err = c.Delete(deleteURL(c, metricID), requestOpts) return } golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/metrics/results.go000066400000000000000000000067031467426574700312360ustar00rootroot00000000000000package metrics import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/pagination" "github.com/gophercloud/utils/gnocchi/metric/v1/archivepolicies" "github.com/gophercloud/utils/gnocchi/metric/v1/resources" ) type commonResult struct { gophercloud.Result } // Extract is a function that accepts a result and extracts a Gnocchi metric. func (r commonResult) Extract() (*Metric, error) { var s *Metric err := r.ExtractInto(&s) return s, err } // GetResult represents the result of a get operation. Call its Extract // method to interpret it as a metric. type GetResult struct { commonResult } // CreateResult represents the result of a create operation. Call its Extract // method to interpret it as a Gnocchi metric. type CreateResult struct { commonResult } // DeleteResult represents the result of a delete operation. Call its // ExtractErr method to determine if the request succeeded or failed. type DeleteResult struct { gophercloud.ErrResult } // Metric is an entity storing aggregates identified by an UUID. // It can be attached to a resource using a name. // How a metric stores its aggregates is defined by the archive policy // it is associated to. type Metric struct { // ArchivePolicy is a Gnocchi archive policy that describes the aggregate // storage policy of a metric. ArchivePolicy archivepolicies.ArchivePolicy `json:"archive_policy"` // ArchivePolicyName is a name of the Gnocchi archive policy that describes // the aggregate storage policy of a metric. // Usually that field is not empty if a Metric struct is a result // from a create request. ArchivePolicyName string `json:"archive_policy_name"` // CreatedByProjectID contains the id of the Identity project that // was used for a metric creation. CreatedByProjectID string `json:"created_by_project_id"` // CreatedByUserID contains the id of the Identity user // that created the Gnocchi metric. CreatedByUserID string `json:"created_by_user_id"` // Creator shows who created the metric. // Usually it contains concatenated string with values from // "created_by_user_id" and "created_by_project_id" fields. Creator string `json:"creator"` // ID uniquely identifies the Gnocchi metric. ID string `json:"id"` // Name is a human-readable name for the Gnocchi metric. Name string `json:"name"` // ResourceID identifies the associated Gnocchi resource of the metric. ResourceID string `json:"resource_id"` // Resource is a Gnocchi resource representation. Resource resources.Resource `json:"resource"` // Unit is a unit of measurement for measures of that Gnocchi metric. Unit string `json:"unit"` } // MetricPage is the page returned by a pager when traversing over a collection // of metrics. type MetricPage struct { pagination.MarkerPageBase } // IsEmpty checks whether a MetricPage struct is empty. func (r MetricPage) IsEmpty() (bool, error) { is, err := ExtractMetrics(r) return len(is) == 0, err } // LastMarker returns the last metric ID in a ListResult. func (r MetricPage) LastMarker() (string, error) { metrics, err := ExtractMetrics(r) if err != nil { return "", err } if len(metrics) == 0 { return "", nil } return metrics[len(metrics)-1].ID, nil } // ExtractMetrics interprets the results of a single page from a List() call, // producing a slice of Metric structs. func ExtractMetrics(r pagination.Page) ([]Metric, error) { var s []Metric err := (r.(MetricPage)).ExtractInto(&s) if err != nil { return nil, err } return s, err } golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/metrics/testing/000077500000000000000000000000001467426574700306555ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/metrics/testing/doc.go000066400000000000000000000000461467426574700317510ustar00rootroot00000000000000// metrics unit tests package testing fixtures.go000066400000000000000000000152771467426574700330120ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/metrics/testingpackage testing import ( "github.com/gophercloud/utils/gnocchi/metric/v1/archivepolicies" "github.com/gophercloud/utils/gnocchi/metric/v1/metrics" ) // MetricsListResult represents a raw server response from a server to a list call. const MetricsListResult = `[ { "archive_policy": { "aggregation_methods": [ "max", "min" ], "back_window": 0, "definition": [ { "granularity": "1:00:00", "points": 2304, "timespan": "96 days, 0:00:00" }, { "granularity": "0:05:00", "points": 9216, "timespan": "32 days, 0:00:00" }, { "granularity": "1 day, 0:00:00", "points": 400, "timespan": "400 days, 0:00:00" } ], "name": "precise" }, "created_by_project_id": "e9dc821ca664406e981820a477e9a761", "created_by_user_id": "a23c5b98d42d4df3b961e54d5167eb6d", "creator": "a23c5b98d42d4df3b961e54d5167eb6d:e9dc821ca664406e981820a477e9a761", "id": "777a01d6-4694-49cb-b86a-5ba9fd4e609e", "name": "memory.usage", "resource_id": "1f3a0724-1807-4bd1-81f9-ee18c8ff6ccc", "unit": "MB" }, { "archive_policy": { "aggregation_methods": [ "mean", "sum" ], "back_window": 12, "definition": [ { "granularity": "1:00:00", "points": 2160, "timespan": "90 days, 0:00:00" }, { "granularity": "1 day, 0:00:00", "points": 200, "timespan": "200 days, 0:00:00" } ], "name": "not_so_precise" }, "created_by_project_id": "c6b68a6b413648b0a0eb191bf3222f4d", "created_by_user_id": "cb072aacdb494419aeeba5f1c62d1a65", "creator": "cb072aacdb494419aeeba5f1c62d1a65:c6b68a6b413648b0a0eb191bf3222f4d", "id": "6dbc97c5-bfdf-47a2-b184-02e7fa348d21", "name": "cpu.delta", "resource_id": "c5dc0c47-f43c-425c-a82f-44d61ee91175", "unit": "ns" } ]` // Metric1 is an expected representation of a first metric from the MetricsListResult. var Metric1 = metrics.Metric{ ArchivePolicy: archivepolicies.ArchivePolicy{ AggregationMethods: []string{ "max", "min", }, BackWindow: 0, Definition: []archivepolicies.ArchivePolicyDefinition{ { Granularity: "1:00:00", Points: 2304, TimeSpan: "96 days, 0:00:00", }, { Granularity: "0:05:00", Points: 9216, TimeSpan: "32 days, 0:00:00", }, { Granularity: "1 day, 0:00:00", Points: 400, TimeSpan: "400 days, 0:00:00", }, }, Name: "precise", }, CreatedByProjectID: "e9dc821ca664406e981820a477e9a761", CreatedByUserID: "a23c5b98d42d4df3b961e54d5167eb6d", Creator: "a23c5b98d42d4df3b961e54d5167eb6d:e9dc821ca664406e981820a477e9a761", ID: "777a01d6-4694-49cb-b86a-5ba9fd4e609e", Name: "memory.usage", ResourceID: "1f3a0724-1807-4bd1-81f9-ee18c8ff6ccc", Unit: "MB", } // Metric2 is an expected representation of a second metric from the MetricsListResult. var Metric2 = metrics.Metric{ ArchivePolicy: archivepolicies.ArchivePolicy{ AggregationMethods: []string{ "mean", "sum", }, BackWindow: 12, Definition: []archivepolicies.ArchivePolicyDefinition{ { Granularity: "1:00:00", Points: 2160, TimeSpan: "90 days, 0:00:00", }, { Granularity: "1 day, 0:00:00", Points: 200, TimeSpan: "200 days, 0:00:00", }, }, Name: "not_so_precise", }, CreatedByProjectID: "c6b68a6b413648b0a0eb191bf3222f4d", CreatedByUserID: "cb072aacdb494419aeeba5f1c62d1a65", Creator: "cb072aacdb494419aeeba5f1c62d1a65:c6b68a6b413648b0a0eb191bf3222f4d", ID: "6dbc97c5-bfdf-47a2-b184-02e7fa348d21", Name: "cpu.delta", ResourceID: "c5dc0c47-f43c-425c-a82f-44d61ee91175", Unit: "ns", } // MetricGetResult represents a raw server response from a server to a get request. const MetricGetResult = ` { "archive_policy": { "aggregation_methods": [ "mean", "sum" ], "back_window": 12, "definition": [ { "granularity": "1:00:00", "points": 2160, "timespan": "90 days, 0:00:00" }, { "granularity": "1 day, 0:00:00", "points": 200, "timespan": "200 days, 0:00:00" } ], "name": "not_so_precise" }, "created_by_project_id": "c6b68a6b413648b0a0eb191bf3222f4d", "created_by_user_id": "cb072aacdb494419aeeba5f1c62d1a65", "creator": "cb072aacdb494419aeeba5f1c62d1a65:c6b68a6b413648b0a0eb191bf3222f4d", "id": "0ddf61cf-3747-4f75-bf13-13c28ff03ae3", "name": "network.incoming.packets.rate", "resource": { "created_by_project_id": "c6b68a6b413648b0a0eb191bf3222f4d", "created_by_user_id": "cb072aacdb494419aeeba5f1c62d1a65", "creator": "cb072aacdb494419aeeba5f1c62d1a65:c6b68a6b413648b0a0eb191bf3222f4d", "ended_at": null, "id": "75274f99-faf6-4112-a6d5-2794cb07c789", "original_resource_id": "75274f99-faf6-4112-a6d5-2794cb07c789", "project_id": "4154f08883334e0494c41155c33c0fc9", "revision_end": null, "revision_start": "2018-01-08T00:59:33.767815+00:00", "started_at": "2018-01-08T00:59:33.767795+00:00", "type": "compute_instance_network", "user_id": "bd5874d666624b24a9f01c128871e4ac" }, "unit": "packet/s" } ` // MetricCreateRequest represents a request to create a metric. const MetricCreateRequest = ` { "archive_policy_name": "high", "name": "network.incoming.bytes.rate", "resource_id": "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55", "unit": "B/s" } ` // MetricCreateResponse represents a raw server responce to the MetricCreateRequest. const MetricCreateResponse = ` { "archive_policy_name": "high", "created_by_project_id": "3d40ca37-7234-4911-8987b9f288f4ae84", "created_by_user_id": "fdcfb420-c096-45e6-9e177a0bb1950884", "creator": "fdcfb420-c096-45e6-9e177a0bb1950884:3d40ca37-7234-4911-8987b9f288f4ae84", "id": "01b2953e-de74-448a-a305-c84440697933", "name": "network.incoming.bytes.rate", "resource_id": "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55", "unit": "B/s" } ` requests_test.go000066400000000000000000000125661467426574700340510ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/metrics/testingpackage testing import ( "fmt" "net/http" "testing" "time" "github.com/gophercloud/gophercloud/pagination" th "github.com/gophercloud/gophercloud/testhelper" "github.com/gophercloud/utils/gnocchi/metric/v1/archivepolicies" "github.com/gophercloud/utils/gnocchi/metric/v1/metrics" "github.com/gophercloud/utils/gnocchi/metric/v1/resources" fake "github.com/gophercloud/utils/gnocchi/testhelper/client" ) func TestList(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() th.Mux.HandleFunc("/v1/metric", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) r.ParseForm() marker := r.Form.Get("marker") switch marker { case "": fmt.Fprintf(w, MetricsListResult) case "6dbc97c5-bfdf-47a2-b184-02e7fa348d21": fmt.Fprintf(w, `[]`) default: t.Fatalf("/v1/metric invoked with unexpected marker=[%s]", marker) } }) count := 0 metrics.List(fake.ServiceClient(), metrics.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { count++ actual, err := metrics.ExtractMetrics(page) if err != nil { t.Errorf("Failed to extract metrics: %v", err) return false, nil } expected := []metrics.Metric{ Metric1, Metric2, } th.CheckDeepEquals(t, expected, actual) return true, nil }) if count != 1 { t.Errorf("Expected 1 page, got %d", count) } } func TestGet(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() th.Mux.HandleFunc("/v1/metric/0ddf61cf-3747-4f75-bf13-13c28ff03ae3", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) fmt.Fprintf(w, MetricGetResult) }) s, err := metrics.Get(fake.ServiceClient(), "0ddf61cf-3747-4f75-bf13-13c28ff03ae3").Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, s.ArchivePolicy, archivepolicies.ArchivePolicy{ AggregationMethods: []string{ "mean", "sum", }, BackWindow: 12, Definition: []archivepolicies.ArchivePolicyDefinition{ { Granularity: "1:00:00", Points: 2160, TimeSpan: "90 days, 0:00:00", }, { Granularity: "1 day, 0:00:00", Points: 200, TimeSpan: "200 days, 0:00:00", }, }, Name: "not_so_precise", }) th.AssertEquals(t, s.CreatedByProjectID, "c6b68a6b413648b0a0eb191bf3222f4d") th.AssertEquals(t, s.CreatedByUserID, "cb072aacdb494419aeeba5f1c62d1a65") th.AssertEquals(t, s.Creator, "cb072aacdb494419aeeba5f1c62d1a65:c6b68a6b413648b0a0eb191bf3222f4d") th.AssertEquals(t, s.ID, "0ddf61cf-3747-4f75-bf13-13c28ff03ae3") th.AssertEquals(t, s.Name, "network.incoming.packets.rate") th.AssertDeepEquals(t, s.Resource, resources.Resource{ CreatedByProjectID: "c6b68a6b413648b0a0eb191bf3222f4d", CreatedByUserID: "cb072aacdb494419aeeba5f1c62d1a65", Creator: "cb072aacdb494419aeeba5f1c62d1a65:c6b68a6b413648b0a0eb191bf3222f4d", ID: "75274f99-faf6-4112-a6d5-2794cb07c789", OriginalResourceID: "75274f99-faf6-4112-a6d5-2794cb07c789", ProjectID: "4154f08883334e0494c41155c33c0fc9", RevisionStart: time.Date(2018, 1, 8, 00, 59, 33, 767815000, time.UTC), RevisionEnd: time.Time{}, StartedAt: time.Date(2018, 1, 8, 00, 59, 33, 767795000, time.UTC), EndedAt: time.Time{}, Type: "compute_instance_network", UserID: "bd5874d666624b24a9f01c128871e4ac", ExtraAttributes: map[string]interface{}{}, }) th.AssertEquals(t, s.Unit, "packet/s") } func TestCreate(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() th.Mux.HandleFunc("/v1/metric", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, MetricCreateRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) fmt.Fprintf(w, MetricCreateResponse) }) opts := metrics.CreateOpts{ ArchivePolicyName: "high", Name: "network.incoming.bytes.rate", ResourceID: "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55", Unit: "B/s", } s, err := metrics.Create(fake.ServiceClient(), opts).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, s.ArchivePolicyName, "high") th.AssertEquals(t, s.CreatedByProjectID, "3d40ca37-7234-4911-8987b9f288f4ae84") th.AssertEquals(t, s.CreatedByUserID, "fdcfb420-c096-45e6-9e177a0bb1950884") th.AssertEquals(t, s.Creator, "fdcfb420-c096-45e6-9e177a0bb1950884:3d40ca37-7234-4911-8987b9f288f4ae84") th.AssertEquals(t, s.ID, "01b2953e-de74-448a-a305-c84440697933") th.AssertEquals(t, s.Name, "network.incoming.bytes.rate") th.AssertEquals(t, s.ResourceID, "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55") th.AssertEquals(t, s.Unit, "B/s") } func TestDelete(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() th.Mux.HandleFunc("/v1/metric/01b2953e-de74-448a-a305-c84440697933", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusNoContent) }) res := metrics.Delete(fake.ServiceClient(), "01b2953e-de74-448a-a305-c84440697933") th.AssertNoErr(t, res.Err) } golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/metrics/urls.go000066400000000000000000000012301467426574700305100ustar00rootroot00000000000000package metrics import "github.com/gophercloud/gophercloud" const resourcePath = "metric" func resourceURL(c *gophercloud.ServiceClient, metricID string) string { return c.ServiceURL(resourcePath, metricID) } func rootURL(c *gophercloud.ServiceClient) string { return c.ServiceURL(resourcePath) } func listURL(c *gophercloud.ServiceClient) string { return rootURL(c) } func getURL(c *gophercloud.ServiceClient, metricID string) string { return resourceURL(c, metricID) } func createURL(c *gophercloud.ServiceClient) string { return rootURL(c) } func deleteURL(c *gophercloud.ServiceClient, metricID string) string { return resourceURL(c, metricID) } golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/resources/000077500000000000000000000000001467426574700275445ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/resources/doc.go000066400000000000000000000072701467426574700306460ustar00rootroot00000000000000/* Package resources provides the ability to retrieve resources through the Gnocchi API. Example of Listing resources resourceType: "instance", listOpts := resources.ListOpts{ Details: True, } allPages, err := resources.List(gnocchiClient, listOpts, resourceType).AllPages() if err != nil { panic(err) } allResources, err := resources.ExtractResources(allPages) if err != nil { panic(err) } for _, resource := range allResources { fmt.Printf("%+v\n", resource) } Example of Getting a resource resourceID = "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55" resourceType = "generic" resource, err := resources.Get(gnocchiClient, resourceType, resourceID).Extract() if err != nil { panic(err) } Example of Creating a resource without a metric createOpts := resources.CreateOpts{ ID: "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55", } resourceType = "generic" resource, err := resources.Create(gnocchiClient, resourceType, createOpts).Extract() if err != nil { panic(err) } Example of Creating a resource with links to some existing metrics with a starting timestamp of the resource startedAt := time.Date(2018, 1, 4, 10, 0, 0, 0, time.UTC) createOpts := resources.CreateOpts{ ID: "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55", ProjectID: "4154f088-8333-4e04-94c4-1155c33c0fc9", StartedAt: &startedAt, Metrics: map[string]interface{}{ "disk.read.bytes.rate": "ed1bb76f-6ccc-4ad2-994c-dbb19ddccbae", "disk.write.bytes.rate": "0a2da84d-4753-43f5-a65f-0f8d44d2766c", }, } resourceType = "compute_instance_disk" resource, err := resources.Create(gnocchiClient, resourceType, createOpts).Extract() if err != nil { panic(err) } Example of Creating a resource and a metric a the same time createOpts := resources.CreateOpts{ ID: "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55", ProjectID: "4154f088-8333-4e04-94c4-1155c33c0fc9", UserID: "bd5874d6-6662-4b24-a9f01c128871e4ac", Metrics: map[string]interface{}{ "cpu.delta": map[string]string{ "archive_policy_name": "medium", }, }, } resourceType = "compute_instance" resource, err := resources.Create(gnocchiClient, resourceType, createOpts).Extract() if err != nil { panic(err) } Example of Updating a resource updateOpts := resources.UpdateOpts{ ProjectID: "4154f08883334e0494c41155c33c0fc9", } resourceType = "generic" resourceID = "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55" resource, err := resources.Update(gnocchiClient, resourceType, resourceID, updateOpts).Extract() if err != nil { panic(err) } Example of Updating a resource and associating an existing metric to it endedAt := time.Date(2018, 1, 16, 12, 0, 0, 0, time.UTC) metrics := map[string]interface{}{ "disk.write.bytes.rate": "0a2da84d-4753-43f5-a65f-0f8d44d2766c", } updateOpts := resources.UpdateOpts{ EndedAt: &endedAt, Metrics: &metrics, } resourceType = "compute_instance_disk" resourceID = "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55" resource, err := resources.Update(gnocchiClient, resourceType, resourceID, updateOpts).Extract() if err != nil { panic(err) } Example of Updating a resource and creating an associated metric at the same time metrics := map[string]interface{}{ "cpu.delta": map[string]string{ "archive_policy_name": "medium", }, } updateOpts := resources.UpdateOpts{ Metrics: &metrics, } resourceType = "compute_instance" resourceID = "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55" resource, err := resources.Update(gnocchiClient, resourceType, resourceID, updateOpts).Extract() if err != nil { panic(err) } Example of Deleting a Gnocchi resource resourceID := "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55" err := resources.Delete(gnocchiClient, resourceType, resourceID).ExtractErr() if err != nil { panic(err) } */ package resources golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/resources/requests.go000066400000000000000000000145711467426574700317560ustar00rootroot00000000000000package resources import ( "time" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/pagination" "github.com/gophercloud/utils/gnocchi" ) // ListOptsBuilder allows extensions to add additional parameters to the // List request. type ListOptsBuilder interface { ToResourceListQuery() (string, error) } // ListOpts allows the limiting and sorting of paginated collections through // the Gnocchi API. type ListOpts struct { // Details allows to list resources with all attributes. Details bool `q:"details"` // Limit allows to limits count of resources in the response. Limit int `q:"limit"` // Marker is used for pagination. Marker string `q:"marker"` // SortKey allows to sort resources in the response by key. SortKey string `q:"sort_key"` // SortDir allows to set the direction of sorting. // Can be `asc` or `desc`. SortDir string `q:"sort_dir"` } // ToResourceListQuery formats a ListOpts into a query string. func (opts ListOpts) ToResourceListQuery() (string, error) { q, err := gophercloud.BuildQueryString(opts) return q.String(), err } // List returns a Pager which allows you to iterate over a collection of // resources. It accepts a ListOpts struct, which allows you to limit and sort // the returned collection for a greater efficiency. func List(c *gophercloud.ServiceClient, opts ListOptsBuilder, resourceType string) pagination.Pager { url := listURL(c, resourceType) if opts != nil { query, err := opts.ToResourceListQuery() if err != nil { return pagination.Pager{Err: err} } url += query } pager := pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { p := ResourcePage{pagination.MarkerPageBase{PageResult: r}} p.MarkerPageBase.Owner = p return p }) return pager } // Get retrieves a specific Gnocchi resource based on its type and ID. func Get(c *gophercloud.ServiceClient, resourceType string, resourceID string) (r GetResult) { _, r.Err = c.Get(getURL(c, resourceType, resourceID), &r.Body, nil) return } // CreateOptsBuilder allows to add additional parameters to the // Create request. type CreateOptsBuilder interface { ToResourceCreateMap() (map[string]interface{}, error) } // CreateOpts specifies parameters of a new Gnocchi resource. type CreateOpts struct { // ID uniquely identifies the Gnocchi resource. ID string `json:"id" required:"true"` // Metrics field can be used to link existing metrics in the resource // or to create metrics with the resource at the same time to save // some requests. Metrics map[string]interface{} `json:"metrics,omitempty"` // ProjectID is the Identity project of the resource. ProjectID string `json:"project_id,omitempty"` // UserID is the Identity user of the resource. UserID string `json:"user_id,omitempty"` // StartedAt is a resource creation timestamp. StartedAt *time.Time `json:"-"` // EndedAt is a timestamp of when the resource has ended. EndedAt *time.Time `json:"-"` // ExtraAttributes is a collection of keys and values that can be found in resources // of different resource types. ExtraAttributes map[string]interface{} `json:"-"` } // ToResourceCreateMap constructs a request body from CreateOpts. func (opts CreateOpts) ToResourceCreateMap() (map[string]interface{}, error) { b, err := gophercloud.BuildRequestBody(opts, "") if err != nil { return nil, err } if opts.StartedAt != nil { b["started_at"] = opts.StartedAt.Format(gnocchi.RFC3339NanoTimezone) } if opts.EndedAt != nil { b["ended_at"] = opts.EndedAt.Format(gnocchi.RFC3339NanoTimezone) } if opts.ExtraAttributes != nil { for key, value := range opts.ExtraAttributes { b[key] = value } } return b, nil } // Create requests the creation of a new Gnocchi resource on the server. func Create(client *gophercloud.ServiceClient, resourceType string, opts CreateOptsBuilder) (r CreateResult) { b, err := opts.ToResourceCreateMap() if err != nil { r.Err = err return } _, r.Err = client.Post(createURL(client, resourceType), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{201}, }) return } // UpdateOptsBuilder allows extensions to add additional parameters to the // Update request. type UpdateOptsBuilder interface { ToResourceUpdateMap() (map[string]interface{}, error) } // UpdateOpts represents options used to update a network. type UpdateOpts struct { // Metrics field can be used to link existing metrics in the resource // or to create metrics and update the resource at the same time to save // some requests. Metrics *map[string]interface{} `json:"metrics,omitempty"` // ProjectID is the Identity project of the resource. ProjectID string `json:"project_id,omitempty"` // UserID is the Identity user of the resource. UserID string `json:"user_id,omitempty"` // StartedAt is a resource creation timestamp. StartedAt *time.Time `json:"-"` // EndedAt is a timestamp of when the resource has ended. EndedAt *time.Time `json:"-"` // ExtraAttributes is a collection of keys and values that can be found in resources // of different resource types. ExtraAttributes map[string]interface{} `json:"-"` } // ToResourceUpdateMap builds a request body from UpdateOpts. func (opts UpdateOpts) ToResourceUpdateMap() (map[string]interface{}, error) { b, err := gophercloud.BuildRequestBody(opts, "") if err != nil { return nil, err } if opts.StartedAt != nil { b["started_at"] = opts.StartedAt.Format(gnocchi.RFC3339NanoTimezone) } if opts.EndedAt != nil { b["ended_at"] = opts.EndedAt.Format(gnocchi.RFC3339NanoTimezone) } if opts.ExtraAttributes != nil { for key, value := range opts.ExtraAttributes { b[key] = value } } return b, nil } // Update accepts a UpdateOpts struct and updates an existing Gnocchi resource using the // values provided. func Update(c *gophercloud.ServiceClient, resourceType, resourceID string, opts UpdateOptsBuilder) (r UpdateResult) { b, err := opts.ToResourceUpdateMap() if err != nil { r.Err = err return } _, r.Err = c.Patch(updateURL(c, resourceType, resourceID), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, }) return } // Delete accepts a unique ID and deletes the Gnocchi resource associated with it. func Delete(c *gophercloud.ServiceClient, resourceType, resourceID string) (r DeleteResult) { requestOpts := &gophercloud.RequestOpts{ MoreHeaders: map[string]string{ "Accept": "application/json, */*", }, } _, r.Err = c.Delete(deleteURL(c, resourceType, resourceID), requestOpts) return } golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/resources/results.go000066400000000000000000000125121467426574700315750ustar00rootroot00000000000000package resources import ( "encoding/json" "time" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/pagination" "github.com/gophercloud/utils/gnocchi" "github.com/gophercloud/utils/internal" ) type commonResult struct { gophercloud.Result } // Extract is a function that accepts a result and extracts a Gnocchi resource. func (r commonResult) Extract() (*Resource, error) { var s *Resource err := r.ExtractInto(&s) return s, err } // GetResult represents the result of a get operation. Call its Extract // method to interpret it as a Gnocchi resource. type GetResult struct { commonResult } // CreateResult represents the result of a create operation. Call its Extract // method to interpret it as a Gnocchi resource. type CreateResult struct { commonResult } // UpdateResult represents the result of an update operation. Call its Extract // method to interpret it as a Gnocchi resource. type UpdateResult struct { commonResult } // DeleteResult represents the result of a delete operation. Call its // ExtractErr method to determine if the request succeeded or failed. type DeleteResult struct { gophercloud.ErrResult } // Resource is an entity representing anything in your infrastructure // that you will associate metric(s) with. // It is identified by a unique ID and can contain attributes. type Resource struct { // CreatedByProjectID contains the id of the Identity project that // was used for a resource creation. CreatedByProjectID string `json:"created_by_project_id"` // CreatedByUserID contains the id of the Identity user // that created the Gnocchi resource. CreatedByUserID string `json:"created_by_user_id"` // Creator shows who created the resource. // Usually it contains concatenated string with values from // "created_by_user_id" and "created_by_project_id" fields. Creator string `json:"creator"` // ID uniquely identifies the Gnocchi resource. ID string `json:"id"` // Metrics are entities that store aggregates. Metrics map[string]string `json:"metrics"` // OriginalResourceID is the orginal resource id. It can be different from the // regular ID field. OriginalResourceID string `json:"original_resource_id"` // ProjectID is the Identity project of the resource. ProjectID string `json:"project_id"` // RevisionStart is a staring timestamp of the current resource revision. RevisionStart time.Time `json:"-"` // RevisionEnd is an ending timestamp of the last resource revision. RevisionEnd time.Time `json:"-"` // StartedAt is a resource creation timestamp. StartedAt time.Time `json:"-"` // EndedAt is a timestamp of when the resource has ended. EndedAt time.Time `json:"-"` // Type is a type of the resource. Type string `json:"type"` // UserID is the Identity user of the resource. UserID string `json:"user_id"` // ExtraAttributes is a collection of keys and values that can be found in resources // of different resource types. ExtraAttributes map[string]interface{} `json:"-"` } // UnmarshalJSON helps to unmarshal Resource fields into needed values. func (r *Resource) UnmarshalJSON(b []byte) error { type tmp Resource var s struct { tmp ExtraAttributes map[string]interface{} `json:"extra_attributes"` RevisionStart gnocchi.JSONRFC3339NanoTimezone `json:"revision_start"` RevisionEnd gnocchi.JSONRFC3339NanoTimezone `json:"revision_end"` StartedAt gnocchi.JSONRFC3339NanoTimezone `json:"started_at"` EndedAt gnocchi.JSONRFC3339NanoTimezone `json:"ended_at"` } err := json.Unmarshal(b, &s) if err != nil { return err } *r = Resource(s.tmp) // Collect Gnocchi timestamps. r.RevisionStart = time.Time(s.RevisionStart) r.RevisionEnd = time.Time(s.RevisionEnd) r.StartedAt = time.Time(s.StartedAt) r.EndedAt = time.Time(s.EndedAt) // Collect other resource fields // and bundle them into ExtraAttributes. if s.ExtraAttributes != nil { r.ExtraAttributes = s.ExtraAttributes } else { var result interface{} err := json.Unmarshal(b, &result) if err != nil { return err } if resultMap, ok := result.(map[string]interface{}); ok { delete(resultMap, "revision_start") delete(resultMap, "revision_end") delete(resultMap, "started_at") delete(resultMap, "ended_at") r.ExtraAttributes = internal.RemainingKeys(Resource{}, resultMap) } } return err } // ResourcePage abstracts the raw results of making a List() request against // the Gnocchi API. // // As Gnocchi API may freely alter the response bodies of structures // returned to the client, you may only safely access the data provided through // the ExtractResources call. type ResourcePage struct { pagination.MarkerPageBase } // IsEmpty checks whether a ResourcePage struct is empty. func (r ResourcePage) IsEmpty() (bool, error) { is, err := ExtractResources(r) return len(is) == 0, err } // LastMarker returns the last resource ID in a ListResult. func (r ResourcePage) LastMarker() (string, error) { resources, err := ExtractResources(r) if err != nil { return "", err } if len(resources) == 0 { return "", nil } return resources[len(resources)-1].ID, nil } // ExtractResources interprets the results of a single page from a List() call, // producing a slice of Resource structs. func ExtractResources(r pagination.Page) ([]Resource, error) { var s []Resource err := (r.(ResourcePage)).ExtractInto(&s) if err != nil { return nil, err } return s, err } golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/resources/testing/000077500000000000000000000000001467426574700312215ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/resources/testing/doc.go000066400000000000000000000000501467426574700323100ustar00rootroot00000000000000// resources unit tests package testing fixtures.go000066400000000000000000000273221467426574700333500ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/resources/testingpackage testing import ( "time" "github.com/gophercloud/utils/gnocchi/metric/v1/resources" ) // ResourceListResult represents raw server response from a server to a list call. const ResourceListResult = `[ { "created_by_project_id": "3d40ca37723449118987b9f288f4ae84", "created_by_user_id": "fdcfb420c09645e69e177a0bb1950884", "creator": "fdcfb420c09645e69e177a0bb1950884:3d40ca37723449118987b9f288f4ae84", "display_name": "MyInstance00", "flavor_name": "2CPU4G", "host": "compute010", "ended_at": null, "id": "1f3a0724-1807-4bd1-81f9-ee18c8ff6ccc", "metrics": { "cpu.delta": "2df1515e-6325-4d49-af0d-1052f6462fe4", "memory.usage": "777a01d6-4694-49cb-b86a-5ba9fd4e609e" }, "original_resource_id": "1f3a0724-1807-4bd1-81f9-ee18c8ff6ccc", "project_id": "4154f08883334e0494c41155c33c0fc9", "revision_end": null, "revision_start": "2018-01-02T11:39:33.942419+00:00", "started_at": "2018-01-02T11:39:33.942391+00:00", "type": "compute_instance", "user_id": "bd5874d666624b24a9f01c128871e4ac" }, { "created_by_project_id": "3d40ca37723449118987b9f288f4ae84", "created_by_user_id": "fdcfb420c09645e69e177a0bb1950884", "creator": "fdcfb420c09645e69e177a0bb1950884:3d40ca37723449118987b9f288f4ae84", "disk_device_name": "sdb", "ended_at": null, "id": "789a7f65-977d-40f4-beed-f717100125f5", "metrics": { "disk.read.bytes.rate": "ed1bb76f-6ccc-4ad2-994c-dbb19ddccbae", "disk.write.bytes.rate": "0a2da84d-4753-43f5-a65f-0f8d44d2766c" }, "original_resource_id": "789a7f65-977d-40f4-beed-f717100125f5", "project_id": "4154f08883334e0494c41155c33c0fc9", "revision_end": null, "revision_start": "2018-01-03T11:44:31.155773+00:00", "started_at": "2018-01-03T11:44:31.155732+00:00", "type": "compute_instance_disk", "user_id": "bd5874d666624b24a9f01c128871e4ac" } ]` // Resource1 is an expected representation of a first resource from the ResourceListResult. var Resource1 = resources.Resource{ CreatedByProjectID: "3d40ca37723449118987b9f288f4ae84", CreatedByUserID: "fdcfb420c09645e69e177a0bb1950884", Creator: "fdcfb420c09645e69e177a0bb1950884:3d40ca37723449118987b9f288f4ae84", ID: "1f3a0724-1807-4bd1-81f9-ee18c8ff6ccc", Metrics: map[string]string{ "cpu.delta": "2df1515e-6325-4d49-af0d-1052f6462fe4", "memory.usage": "777a01d6-4694-49cb-b86a-5ba9fd4e609e", }, OriginalResourceID: "1f3a0724-1807-4bd1-81f9-ee18c8ff6ccc", ProjectID: "4154f08883334e0494c41155c33c0fc9", RevisionStart: time.Date(2018, 1, 2, 11, 39, 33, 942419000, time.UTC), RevisionEnd: time.Time{}, StartedAt: time.Date(2018, 1, 2, 11, 39, 33, 942391000, time.UTC), EndedAt: time.Time{}, Type: "compute_instance", UserID: "bd5874d666624b24a9f01c128871e4ac", ExtraAttributes: map[string]interface{}{ "display_name": "MyInstance00", "flavor_name": "2CPU4G", "host": "compute010", }, } // Resource2 is an expected representation of a second resource from the ResourceListResult. var Resource2 = resources.Resource{ CreatedByProjectID: "3d40ca37723449118987b9f288f4ae84", CreatedByUserID: "fdcfb420c09645e69e177a0bb1950884", Creator: "fdcfb420c09645e69e177a0bb1950884:3d40ca37723449118987b9f288f4ae84", ID: "789a7f65-977d-40f4-beed-f717100125f5", Metrics: map[string]string{ "disk.read.bytes.rate": "ed1bb76f-6ccc-4ad2-994c-dbb19ddccbae", "disk.write.bytes.rate": "0a2da84d-4753-43f5-a65f-0f8d44d2766c", }, OriginalResourceID: "789a7f65-977d-40f4-beed-f717100125f5", ProjectID: "4154f08883334e0494c41155c33c0fc9", RevisionStart: time.Date(2018, 1, 3, 11, 44, 31, 155773000, time.UTC), RevisionEnd: time.Time{}, StartedAt: time.Date(2018, 1, 3, 11, 44, 31, 155732000, time.UTC), EndedAt: time.Time{}, Type: "compute_instance_disk", UserID: "bd5874d666624b24a9f01c128871e4ac", ExtraAttributes: map[string]interface{}{ "disk_device_name": "sdb", }, } // ResourceGetResult represents raw server response from a server to a get requrest. const ResourceGetResult = ` { "created_by_project_id": "3d40ca37723449118987b9f288f4ae84", "created_by_user_id": "fdcfb420c09645e69e177a0bb1950884", "creator": "fdcfb420c09645e69e177a0bb1950884:3d40ca37723449118987b9f288f4ae84", "iface_name": "eth0", "ended_at": null, "id": "75274f99-faf6-4112-a6d5-2794cb07c789", "metrics": { "network.incoming.bytes.rate": "01b2953e-de74-448a-a305-c84440697933", "network.outgoing.bytes.rate": "4ac0041b-3bf7-441d-a95a-d3e2f1691158", "network.incoming.packets.rate": "5a64328e-8a7c-4c6a-99df-2e6d17440142", "network.outgoing.packets.rate": "dc9f3198-155b-4b88-a92c-58a3853ce2b2" }, "original_resource_id": "75274f99-faf6-4112-a6d5-2794cb07c789", "project_id": "4154f08883334e0494c41155c33c0fc9", "revision_end": null, "revision_start": "2018-01-01T11:44:31.742031+00:00", "started_at": "2018-01-01T11:44:31.742011+00:00", "type": "compute_instance_network", "user_id": "bd5874d666624b24a9f01c128871e4ac" } ` // ResourceCreateWithoutMetricsRequest represents a request to create a resource without metrics. const ResourceCreateWithoutMetricsRequest = ` { "id": "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55", "project_id": "4154f088-8333-4e04-94c4-1155c33c0fc9", "user_id": "bd5874d6-6662-4b24-a9f01c128871e4ac" } ` // ResourceCreateWithoutMetricsResult represents a raw server responce to the ResourceCreateNoMetricsRequest. const ResourceCreateWithoutMetricsResult = ` { "created_by_project_id": "3d40ca37-7234-4911-8987b9f288f4ae84", "created_by_user_id": "fdcfb420-c096-45e6-9e177a0bb1950884", "creator": "fdcfb420-c096-45e6-9e177a0bb1950884:3d40ca37-7234-4911-8987b9f288f4ae84", "ended_at": null, "id": "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55", "metrics": {}, "original_resource_id": "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55", "project_id": "4154f088-8333-4e04-94c4-1155c33c0fc9", "revision_end": null, "revision_start": "2018-01-03T11:44:31.155773+00:00", "started_at": "2018-01-03T11:44:31.155732+00:00", "type": "generic", "user_id": "bd5874d6-6662-4b24-a9f01c128871e4ac" } ` // ResourceCreateLinkMetricsRequest represents a request to create a resource with linked metrics. const ResourceCreateLinkMetricsRequest = ` { "id": "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55", "project_id": "4154f088-8333-4e04-94c4-1155c33c0fc9", "user_id": "bd5874d6-6662-4b24-a9f01c128871e4ac", "started_at": "2018-01-02T23:23:34+00:00", "ended_at": "2018-01-04T10:00:12+00:00", "metrics": { "network.incoming.bytes.rate": "01b2953e-de74-448a-a305-c84440697933", "network.outgoing.bytes.rate": "dc9f3198-155b-4b88-a92c-58a3853ce2b2" } } ` // ResourceCreateLinkMetricsResult represents a raw server responce to the ResourceCreateLinkMetricsRequest. const ResourceCreateLinkMetricsResult = ` { "created_by_project_id": "3d40ca37-7234-4911-8987b9f288f4ae84", "created_by_user_id": "fdcfb420-c096-45e6-9e177a0bb1950884", "creator": "fdcfb420-c096-45e6-9e177a0bb1950884:3d40ca37-7234-4911-8987b9f288f4ae84", "id": "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55", "metrics": { "network.incoming.bytes.rate": "01b2953e-de74-448a-a305-c84440697933", "network.outgoing.bytes.rate": "dc9f3198-155b-4b88-a92c-58a3853ce2b2" }, "original_resource_id": "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55", "project_id": "4154f088-8333-4e04-94c4-1155c33c0fc9", "revision_end": null, "revision_start": "2018-01-02T23:23:34.155813+00:00", "ended_at": "2018-01-04T10:00:12+00:00", "started_at": "2018-01-02T23:23:34+00:00", "type": "compute_instance_network", "user_id": "bd5874d6-6662-4b24-a9f01c128871e4ac" } ` // ResourceCreateWithMetricsRequest represents a request to simultaneously create a resource with metrics. const ResourceCreateWithMetricsRequest = ` { "id": "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55", "project_id": "4154f088-8333-4e04-94c4-1155c33c0fc9", "user_id": "bd5874d6-6662-4b24-a9f01c128871e4ac", "ended_at": "2018-01-09T20:00:00+00:00", "metrics": { "disk.write.bytes.rate": { "archive_policy_name": "high" } } } ` // ResourceCreateWithMetricsResult represents a raw server responce to the ResourceCreateWithMetricsRequest. const ResourceCreateWithMetricsResult = ` { "created_by_project_id": "3d40ca37-7234-4911-8987b9f288f4ae84", "created_by_user_id": "fdcfb420-c096-45e6-9e177a0bb1950884", "creator": "fdcfb420-c096-45e6-9e177a0bb1950884:3d40ca37-7234-4911-8987b9f288f4ae84", "id": "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55", "metrics": { "disk.write.bytes.rate": "0a2da84d-4753-43f5-a65f-0f8d44d2766c" }, "original_resource_id": "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55", "project_id": "4154f088-8333-4e04-94c4-1155c33c0fc9", "revision_end": null, "revision_start": "2018-01-02T23:23:34.155813+00:00", "ended_at": "2018-01-09T20:00:00+00:00", "started_at": "2018-01-02T23:23:34.155773+00:00", "type": "compute_instance_disk", "user_id": "bd5874d6-6662-4b24-a9f01c128871e4ac" } ` // ResourceUpdateLinkMetricsRequest represents a request to update a resource and link some existing metrics. const ResourceUpdateLinkMetricsRequest = ` { "ended_at":"2018-01-14T13:00:00+00:00", "metrics": { "network.incoming.bytes.rate": "01b2953e-de74-448a-a305-c84440697933" } } ` // ResourceUpdateLinkMetricsResponse represents a raw server responce to the ResourceUpdateLinkMetricsRequest. const ResourceUpdateLinkMetricsResponse = ` { "created_by_project_id": "3d40ca37-7234-4911-8987b9f288f4ae84", "created_by_user_id": "fdcfb420-c096-45e6-9e177a0bb1950884", "creator": "fdcfb420-c096-45e6-9e177a0bb1950884:3d40ca37-7234-4911-8987b9f288f4ae84", "ended_at": "2018-01-14T13:00:00+00:00", "id": "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55", "project_id": "4154f088-8333-4e04-94c4-1155c33c0fc9", "original_resource_id": "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55", "revision_end": null, "metrics": { "network.incoming.bytes.rate": "01b2953e-de74-448a-a305-c84440697933" }, "revision_start": "2018-01-12T13:44:34.742031+00:00", "started_at": "2018-01-12T13:44:34.742011+00:00", "type": "compute_instance_network", "user_id": "bd5874d6-6662-4b24-a9f01c128871e4ac" } ` // ResourceUpdateCreateMetricsRequest represents a request to update a resource and link some existing metrics. const ResourceUpdateCreateMetricsRequest = ` { "started_at":"2018-01-12T11:00:00+00:00", "metrics": { "disk.read.bytes.rate": { "archive_policy_name": "low" } } } ` // ResourceUpdateCreateMetricsResponse represents a raw server responce to the ResourceUpdateLinkMetricsRequest. const ResourceUpdateCreateMetricsResponse = ` { "created_by_project_id": "3d40ca37-7234-4911-8987b9f288f4ae84", "created_by_user_id": "fdcfb420-c096-45e6-9e177a0bb1950884", "creator": "fdcfb420-c096-45e6-9e177a0bb1950884:3d40ca37-7234-4911-8987b9f288f4ae84", "ended_at": null, "id": "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55", "project_id": "4154f088-8333-4e04-94c4-1155c33c0fc9", "original_resource_id": "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55", "revision_end": null, "metrics": { "disk.read.bytes.rate": "ed1bb76f-6ccc-4ad2-994c-dbb19ddccbae" }, "revision_start": "2018-01-12T12:00:34.742031+00:00", "started_at": "2018-01-12T11:00:00+00:00", "type": "compute_instance_disk", "user_id": "bd5874d6-6662-4b24-a9f01c128871e4ac" } ` requests_test.go000066400000000000000000000336511467426574700344130ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/resources/testingpackage testing import ( "fmt" "net/http" "testing" "time" "github.com/gophercloud/gophercloud/pagination" th "github.com/gophercloud/gophercloud/testhelper" "github.com/gophercloud/utils/gnocchi/metric/v1/resources" fake "github.com/gophercloud/utils/gnocchi/testhelper/client" ) func TestList(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() th.Mux.HandleFunc("/v1/resource/generic", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) r.ParseForm() marker := r.Form.Get("marker") switch marker { case "": fmt.Fprintf(w, ResourceListResult) case "789a7f65-977d-40f4-beed-f717100125f5": fmt.Fprintf(w, `[]`) default: t.Fatalf("/v1/resources invoked with unexpected marker=[%s]", marker) } }) count := 0 resources.List(fake.ServiceClient(), resources.ListOpts{}, "generic").EachPage(func(page pagination.Page) (bool, error) { count++ actual, err := resources.ExtractResources(page) if err != nil { t.Errorf("Failed to extract resources: %v", err) return false, nil } expected := []resources.Resource{ Resource1, Resource2, } th.CheckDeepEquals(t, expected, actual) return true, nil }) if count != 1 { t.Errorf("Expected 1 page, got %d", count) } } func TestGet(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() th.Mux.HandleFunc("/v1/resource/compute_instance_network/75274f99-faf6-4112-a6d5-2794cb07c789", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) fmt.Fprintf(w, ResourceGetResult) }) s, err := resources.Get(fake.ServiceClient(), "compute_instance_network", "75274f99-faf6-4112-a6d5-2794cb07c789").Extract() th.AssertNoErr(t, err) th.AssertEquals(t, s.CreatedByProjectID, "3d40ca37723449118987b9f288f4ae84") th.AssertEquals(t, s.CreatedByUserID, "fdcfb420c09645e69e177a0bb1950884") th.AssertEquals(t, s.Creator, "fdcfb420c09645e69e177a0bb1950884:3d40ca37723449118987b9f288f4ae84") th.AssertEquals(t, s.ID, "75274f99-faf6-4112-a6d5-2794cb07c789") th.AssertDeepEquals(t, s.Metrics, map[string]string{ "network.incoming.bytes.rate": "01b2953e-de74-448a-a305-c84440697933", "network.outgoing.bytes.rate": "4ac0041b-3bf7-441d-a95a-d3e2f1691158", "network.incoming.packets.rate": "5a64328e-8a7c-4c6a-99df-2e6d17440142", "network.outgoing.packets.rate": "dc9f3198-155b-4b88-a92c-58a3853ce2b2", }) th.AssertEquals(t, s.OriginalResourceID, "75274f99-faf6-4112-a6d5-2794cb07c789") th.AssertEquals(t, s.ProjectID, "4154f08883334e0494c41155c33c0fc9") th.AssertEquals(t, s.RevisionStart, time.Date(2018, 01, 01, 11, 44, 31, 742031000, time.UTC)) th.AssertEquals(t, s.RevisionEnd, time.Time{}) th.AssertEquals(t, s.StartedAt, time.Date(2018, 01, 01, 11, 44, 31, 742011000, time.UTC)) th.AssertEquals(t, s.EndedAt, time.Time{}) th.AssertEquals(t, s.Type, "compute_instance_network") th.AssertEquals(t, s.UserID, "bd5874d666624b24a9f01c128871e4ac") th.AssertDeepEquals(t, s.ExtraAttributes, map[string]interface{}{ "iface_name": "eth0", }) } func TestCreateWithoutMetrics(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() th.Mux.HandleFunc("/v1/resource/generic", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ResourceCreateWithoutMetricsRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) fmt.Fprintf(w, ResourceCreateWithoutMetricsResult) }) opts := resources.CreateOpts{ ID: "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55", ProjectID: "4154f088-8333-4e04-94c4-1155c33c0fc9", UserID: "bd5874d6-6662-4b24-a9f01c128871e4ac", } s, err := resources.Create(fake.ServiceClient(), "generic", opts).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, s.CreatedByProjectID, "3d40ca37-7234-4911-8987b9f288f4ae84") th.AssertEquals(t, s.CreatedByUserID, "fdcfb420-c096-45e6-9e177a0bb1950884") th.AssertEquals(t, s.Creator, "fdcfb420-c096-45e6-9e177a0bb1950884:3d40ca37-7234-4911-8987b9f288f4ae84") th.AssertEquals(t, s.ID, "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55") th.AssertDeepEquals(t, s.Metrics, map[string]string{}) th.AssertEquals(t, s.OriginalResourceID, "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55") th.AssertEquals(t, s.ProjectID, "4154f088-8333-4e04-94c4-1155c33c0fc9") th.AssertEquals(t, s.RevisionStart, time.Date(2018, 1, 3, 11, 44, 31, 155773000, time.UTC)) th.AssertEquals(t, s.RevisionEnd, time.Time{}) th.AssertEquals(t, s.StartedAt, time.Date(2018, 1, 3, 11, 44, 31, 155732000, time.UTC)) th.AssertEquals(t, s.EndedAt, time.Time{}) th.AssertEquals(t, s.Type, "generic") th.AssertEquals(t, s.UserID, "bd5874d6-6662-4b24-a9f01c128871e4ac") } func TestCreateLinkMetrics(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() th.Mux.HandleFunc("/v1/resource/compute_instance_network", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ResourceCreateLinkMetricsRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) fmt.Fprintf(w, ResourceCreateLinkMetricsResult) }) startedAt := time.Date(2018, 1, 2, 23, 23, 34, 0, time.UTC) endedAt := time.Date(2018, 1, 4, 10, 00, 12, 0, time.UTC) opts := resources.CreateOpts{ ID: "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55", ProjectID: "4154f088-8333-4e04-94c4-1155c33c0fc9", UserID: "bd5874d6-6662-4b24-a9f01c128871e4ac", StartedAt: &startedAt, EndedAt: &endedAt, Metrics: map[string]interface{}{ "network.incoming.bytes.rate": "01b2953e-de74-448a-a305-c84440697933", "network.outgoing.bytes.rate": "dc9f3198-155b-4b88-a92c-58a3853ce2b2", }, } s, err := resources.Create(fake.ServiceClient(), "compute_instance_network", opts).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, s.CreatedByProjectID, "3d40ca37-7234-4911-8987b9f288f4ae84") th.AssertEquals(t, s.CreatedByUserID, "fdcfb420-c096-45e6-9e177a0bb1950884") th.AssertEquals(t, s.Creator, "fdcfb420-c096-45e6-9e177a0bb1950884:3d40ca37-7234-4911-8987b9f288f4ae84") th.AssertEquals(t, s.ID, "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55") th.AssertDeepEquals(t, s.Metrics, map[string]string{ "network.incoming.bytes.rate": "01b2953e-de74-448a-a305-c84440697933", "network.outgoing.bytes.rate": "dc9f3198-155b-4b88-a92c-58a3853ce2b2", }) th.AssertEquals(t, s.OriginalResourceID, "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55") th.AssertEquals(t, s.ProjectID, "4154f088-8333-4e04-94c4-1155c33c0fc9") th.AssertEquals(t, s.RevisionStart, time.Date(2018, 1, 2, 23, 23, 34, 155813000, time.UTC)) th.AssertEquals(t, s.RevisionEnd, time.Time{}) th.AssertEquals(t, s.StartedAt, time.Date(2018, 1, 2, 23, 23, 34, 0, time.UTC)) th.AssertEquals(t, s.EndedAt, time.Date(2018, 1, 4, 10, 00, 12, 0, time.UTC)) th.AssertEquals(t, s.Type, "compute_instance_network") th.AssertEquals(t, s.UserID, "bd5874d6-6662-4b24-a9f01c128871e4ac") } func TestCreateWithMetrics(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() th.Mux.HandleFunc("/v1/resource/compute_instance_disk", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ResourceCreateWithMetricsRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) fmt.Fprintf(w, ResourceCreateWithMetricsResult) }) endedAt := time.Date(2018, 1, 9, 20, 0, 0, 0, time.UTC) opts := resources.CreateOpts{ ID: "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55", ProjectID: "4154f088-8333-4e04-94c4-1155c33c0fc9", UserID: "bd5874d6-6662-4b24-a9f01c128871e4ac", EndedAt: &endedAt, Metrics: map[string]interface{}{ "disk.write.bytes.rate": map[string]string{ "archive_policy_name": "high", }, }, } s, err := resources.Create(fake.ServiceClient(), "compute_instance_disk", opts).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, s.CreatedByProjectID, "3d40ca37-7234-4911-8987b9f288f4ae84") th.AssertEquals(t, s.CreatedByUserID, "fdcfb420-c096-45e6-9e177a0bb1950884") th.AssertEquals(t, s.Creator, "fdcfb420-c096-45e6-9e177a0bb1950884:3d40ca37-7234-4911-8987b9f288f4ae84") th.AssertEquals(t, s.ID, "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55") th.AssertDeepEquals(t, s.Metrics, map[string]string{ "disk.write.bytes.rate": "0a2da84d-4753-43f5-a65f-0f8d44d2766c", }) th.AssertEquals(t, s.OriginalResourceID, "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55") th.AssertEquals(t, s.ProjectID, "4154f088-8333-4e04-94c4-1155c33c0fc9") th.AssertEquals(t, s.RevisionStart, time.Date(2018, 1, 2, 23, 23, 34, 155813000, time.UTC)) th.AssertEquals(t, s.RevisionEnd, time.Time{}) th.AssertEquals(t, s.StartedAt, time.Date(2018, 1, 2, 23, 23, 34, 155773000, time.UTC)) th.AssertEquals(t, s.EndedAt, time.Date(2018, 1, 9, 20, 00, 00, 0, time.UTC)) th.AssertEquals(t, s.Type, "compute_instance_disk") th.AssertEquals(t, s.UserID, "bd5874d6-6662-4b24-a9f01c128871e4ac") } func TestUpdateLinkMetrics(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() th.Mux.HandleFunc("/v1/resource/compute_instance_network/23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ResourceUpdateLinkMetricsRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) fmt.Fprintf(w, ResourceUpdateLinkMetricsResponse) }) endedAt := time.Date(2018, 1, 14, 13, 0, 0, 0, time.UTC) metrics := map[string]interface{}{ "network.incoming.bytes.rate": "01b2953e-de74-448a-a305-c84440697933", } updateOpts := resources.UpdateOpts{ EndedAt: &endedAt, Metrics: &metrics, } s, err := resources.Update(fake.ServiceClient(), "compute_instance_network", "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55", updateOpts).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, s.CreatedByProjectID, "3d40ca37-7234-4911-8987b9f288f4ae84") th.AssertEquals(t, s.CreatedByUserID, "fdcfb420-c096-45e6-9e177a0bb1950884") th.AssertEquals(t, s.Creator, "fdcfb420-c096-45e6-9e177a0bb1950884:3d40ca37-7234-4911-8987b9f288f4ae84") th.AssertEquals(t, s.ID, "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55") th.AssertDeepEquals(t, s.Metrics, map[string]string{ "network.incoming.bytes.rate": "01b2953e-de74-448a-a305-c84440697933", }) th.AssertEquals(t, s.OriginalResourceID, "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55") th.AssertEquals(t, s.ProjectID, "4154f088-8333-4e04-94c4-1155c33c0fc9") th.AssertEquals(t, s.RevisionStart, time.Date(2018, 1, 12, 13, 44, 34, 742031000, time.UTC)) th.AssertEquals(t, s.RevisionEnd, time.Time{}) th.AssertEquals(t, s.StartedAt, time.Date(2018, 1, 12, 13, 44, 34, 742011000, time.UTC)) th.AssertEquals(t, s.EndedAt, time.Date(2018, 1, 14, 13, 0, 0, 0, time.UTC)) th.AssertEquals(t, s.Type, "compute_instance_network") th.AssertEquals(t, s.UserID, "bd5874d6-6662-4b24-a9f01c128871e4ac") } func TestUpdateCreateMetrics(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() th.Mux.HandleFunc("/v1/resource/compute_instance_network/23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ResourceUpdateCreateMetricsRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) fmt.Fprintf(w, ResourceUpdateCreateMetricsResponse) }) startedAt := time.Date(2018, 1, 12, 11, 0, 0, 0, time.UTC) metrics := map[string]interface{}{ "disk.read.bytes.rate": map[string]string{ "archive_policy_name": "low", }, } updateOpts := resources.UpdateOpts{ StartedAt: &startedAt, Metrics: &metrics, } s, err := resources.Update(fake.ServiceClient(), "compute_instance_network", "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55", updateOpts).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, s.CreatedByProjectID, "3d40ca37-7234-4911-8987b9f288f4ae84") th.AssertEquals(t, s.CreatedByUserID, "fdcfb420-c096-45e6-9e177a0bb1950884") th.AssertEquals(t, s.Creator, "fdcfb420-c096-45e6-9e177a0bb1950884:3d40ca37-7234-4911-8987b9f288f4ae84") th.AssertEquals(t, s.ID, "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55") th.AssertDeepEquals(t, s.Metrics, map[string]string{ "disk.read.bytes.rate": "ed1bb76f-6ccc-4ad2-994c-dbb19ddccbae", }) th.AssertEquals(t, s.OriginalResourceID, "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55") th.AssertEquals(t, s.ProjectID, "4154f088-8333-4e04-94c4-1155c33c0fc9") th.AssertEquals(t, s.RevisionStart, time.Date(2018, 1, 12, 12, 00, 34, 742031000, time.UTC)) th.AssertEquals(t, s.RevisionEnd, time.Time{}) th.AssertEquals(t, s.StartedAt, time.Date(2018, 1, 12, 11, 00, 00, 0, time.UTC)) th.AssertEquals(t, s.EndedAt, time.Time{}) th.AssertEquals(t, s.Type, "compute_instance_disk") th.AssertEquals(t, s.UserID, "bd5874d6-6662-4b24-a9f01c128871e4ac") } func TestDelete(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() th.Mux.HandleFunc("/v1/resource/generic/23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusNoContent) }) res := resources.Delete(fake.ServiceClient(), "generic", "23d5d3f7-9dfa-4f73-b72b-8b0b0063ec55") th.AssertNoErr(t, res.Err) } golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/resources/urls.go000066400000000000000000000017601467426574700310640ustar00rootroot00000000000000package resources import "github.com/gophercloud/gophercloud" const resourcePath = "resource" func rootURL(c *gophercloud.ServiceClient, resourceType string) string { return c.ServiceURL(resourcePath, resourceType) } func resourceURL(c *gophercloud.ServiceClient, resourceType, resourceID string) string { return c.ServiceURL(resourcePath, resourceType, resourceID) } func listURL(c *gophercloud.ServiceClient, resourceType string) string { return rootURL(c, resourceType) } func getURL(c *gophercloud.ServiceClient, resourceType, resourceID string) string { return resourceURL(c, resourceType, resourceID) } func createURL(c *gophercloud.ServiceClient, resourceType string) string { return rootURL(c, resourceType) } func updateURL(c *gophercloud.ServiceClient, resourceType, resourceID string) string { return resourceURL(c, resourceType, resourceID) } func deleteURL(c *gophercloud.ServiceClient, resourceType, resourceID string) string { return resourceURL(c, resourceType, resourceID) } golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/resourcetypes/000077500000000000000000000000001467426574700304465ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/resourcetypes/doc.go000066400000000000000000000045721467426574700315520ustar00rootroot00000000000000/* Package resourcetypes provides ability to manage resource types through the Gnocchi API. Example of Listing resource types allPages, err := resourcetypes.List(client).AllPages() if err != nil { panic(err) } allResourceTypes, err := resourcetypes.ExtractResourceTypes(allPages) if err != nil { panic(err) } for _, resourceType := range allResourceTypes { fmt.Printf("%+v\n", resourceType) } Example of Getting a resource type resourceTypeName := "compute_instance" resourceType, err := resourcetypes.Get(gnocchiClient, resourceTypeName).Extract() if err != nil { panic(err) } Example of Creating a resource type resourceTypeOpts := resourcetypes.CreateOpts{ Name: "compute_instance_network", Attributes: map[string]resourcetypes.AttributeOpts{ "port_name": resourcetypes.AttributeOpts{ Type: "string", Details: map[string]interface{}{ "max_length": 128, "required": false, }, }, "port_id": resourcetypes.AttributeOpts{ Type: "uuid", Details: map[string]interface{}{ "required": true, }, }, }, } resourceType, err := resourcetypes.Create(gnocchiClient, resourceTypeOpts).Extract() if err != nil { panic(err) } Example of Updating a resource type enabledAttributeOptions := resourcetypes.AttributeOpts{ Details: map[string]interface{}{ "required": true, "options": map[string]interface{}{ "fill": true, }, }, Type: "bool", } parendIDAttributeOptions := resourcetypes.AttributeOpts{ Details: map[string]interface{}{ "required": false, }, Type: "uuid", } resourceTypeOpts := resourcetypes.UpdateOpts{ Attributes: []resourcetypes.AttributeUpdateOpts{ { Name: "enabled", Operation: resourcetypes.AttributeAdd, Value: &enabledAttributeOptions, }, { Name: "parent_id", Operation: resourcetypes.AttributeAdd, Value: &parendIDAttributeOptions, }, { Name: "domain_id", Operation: resourcetypes.AttributeRemove, }, }, } resourceType, err := resourcetypes.Update(gnocchiClient, resourceTypeOpts).Extract() if err != nil { panic(err) } Example of Deleting a resource type err := resourcetypes.Delete(gnocchiClient, resourceType).ExtractErr() if err != nil { panic(err) } */ package resourcetypes golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/resourcetypes/requests.go000066400000000000000000000141321467426574700326510ustar00rootroot00000000000000package resourcetypes import ( "fmt" "strings" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/pagination" ) // List makes a request against the Gnocchi API to list resource types. func List(client *gophercloud.ServiceClient) pagination.Pager { return pagination.NewPager(client, listURL(client), func(r pagination.PageResult) pagination.Page { return ResourceTypePage{pagination.SinglePageBase(r)} }) } // Get retrieves a specific Gnocchi resource type based on its name. func Get(c *gophercloud.ServiceClient, resourceTypeName string) (r GetResult) { _, r.Err = c.Get(getURL(c, resourceTypeName), &r.Body, nil) return } // CreateOptsBuilder allows to add additional parameters to the Create request. type CreateOptsBuilder interface { ToResourceTypeCreateMap() (map[string]interface{}, error) } // AttributeOpts represents options of a single resource type attribute that // can be created in the Gnocchi. type AttributeOpts struct { // Type is an attribute type. Type string `json:"type"` // Details represents different attribute fields. Details map[string]interface{} `json:"-"` } // ToMap is a helper function to convert individual AttributeOpts structure into a sub-map. func (opts AttributeOpts) ToMap() (map[string]interface{}, error) { b, err := gophercloud.BuildRequestBody(opts, "") if err != nil { return nil, err } if opts.Details != nil { for k, v := range opts.Details { b[k] = v } } return b, nil } // CreateOpts specifies parameters of a new Gnocchi resource type. type CreateOpts struct { // Attributes is a collection of keys and values of different resource types. Attributes map[string]AttributeOpts `json:"-"` // Name is a human-readable resource type identifier. Name string `json:"name" required:"true"` } // ToResourceTypeCreateMap constructs a request body from CreateOpts. func (opts CreateOpts) ToResourceTypeCreateMap() (map[string]interface{}, error) { b, err := gophercloud.BuildRequestBody(opts, "") if err != nil { return nil, err } // Create resource type without attributes if they're omitted. if opts.Attributes == nil { return b, nil } attributes := make(map[string]interface{}, len(opts.Attributes)) for k, v := range opts.Attributes { attributesMap, err := v.ToMap() if err != nil { return nil, err } attributes[k] = attributesMap } b["attributes"] = attributes return b, nil } // Create requests the creation of a new Gnocchi resource type on the server. func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { b, err := opts.ToResourceTypeCreateMap() if err != nil { r.Err = err return } _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{201}, }) return } // AttributeOperation represents a type of operation that can be performed over // Gnocchi resource type attribute. type AttributeOperation string const ( // AttributeAdd represents Gnocchi resource type attribute add operation. AttributeAdd AttributeOperation = "add" // AttributeRemove represents Gnocchi resource type attribute remove operation. AttributeRemove AttributeOperation = "remove" // AttributeCommonPath represents a prefix for every attribute in the Gnocchi // resource type. AttributeCommonPath = "/attributes" ) // UpdateOptsBuilder allows to add additional parameters to the Update request. type UpdateOptsBuilder interface { ToResourceTypeUpdateMap() ([]map[string]interface{}, error) } // UpdateOpts specifies parameters for a Gnocchi resource type update request. type UpdateOpts struct { // AttributesOperations is a collection of operations that need to be performed // over Gnocchi resource type attributes. Attributes []AttributeUpdateOpts `json:"-"` } // AttributeUpdateOpts represents update options over a single Gnocchi resource // type attribute. type AttributeUpdateOpts struct { // Name is a human-readable name of an attribute that needs to be added or removed. Name string `json:"-" required:"true"` // Operation represent action that needs to be performed over the attribute. Operation AttributeOperation `json:"-" required:"true"` // Value is an attribute options. Value *AttributeOpts `json:"-"` } // ToResourceTypeUpdateMap constructs a request body from UpdateOpts. func (opts UpdateOpts) ToResourceTypeUpdateMap() ([]map[string]interface{}, error) { if len(opts.Attributes) == 0 { return nil, fmt.Errorf("provided Gnocchi resource type UpdateOpts is empty") } updateOptsMaps := make([]map[string]interface{}, len(opts.Attributes)) // Populate a map for every attribute. for i, attributeUpdateOpts := range opts.Attributes { attributeUpdateOptsMap := make(map[string]interface{}) // Populate attribute value map if provided. if attributeUpdateOpts.Value != nil { attributeValue, err := attributeUpdateOpts.Value.ToMap() if err != nil { return nil, err } attributeUpdateOptsMap["value"] = attributeValue } // Populate attribute update operation. attributeUpdateOptsMap["op"] = attributeUpdateOpts.Operation // Populate attribute path from its name. attributeUpdateOptsMap["path"] = strings.Join([]string{ AttributeCommonPath, attributeUpdateOpts.Name, }, "/") updateOptsMaps[i] = attributeUpdateOptsMap } return updateOptsMaps, nil } // Update requests the update operation over existsing Gnocchi resource type. func Update(client *gophercloud.ServiceClient, resourceTypeName string, opts UpdateOptsBuilder) (r UpdateResult) { b, err := opts.ToResourceTypeUpdateMap() if err != nil { r.Err = err return } _, r.Err = client.Patch(updateURL(client, resourceTypeName), b, &r.Body, &gophercloud.RequestOpts{ OkCodes: []int{200}, MoreHeaders: map[string]string{ "Content-Type": "application/json-patch+json", }, }) return } // Delete accepts a human-readable name and deletes the Gnocchi resource type associated with it. func Delete(c *gophercloud.ServiceClient, resourceTypeName string) (r DeleteResult) { requestOpts := &gophercloud.RequestOpts{ MoreHeaders: map[string]string{ "Accept": "application/json, */*", }, } _, r.Err = c.Delete(deleteURL(c, resourceTypeName), requestOpts) return } golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/resourcetypes/results.go000066400000000000000000000071431467426574700325030ustar00rootroot00000000000000package resourcetypes import ( "encoding/json" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/pagination" ) type commonResult struct { gophercloud.Result } // Extract is a function that accepts a result and extracts a Gnocchi resource type. func (r commonResult) Extract() (*ResourceType, error) { var s *ResourceType err := r.ExtractInto(&s) return s, err } // GetResult represents the result of a get operation. Call its Extract // method to interpret it as a Gnocchi resource type. type GetResult struct { commonResult } // CreateResult represents the result of a create operation. Call its Extract // method to interpret it as a Gnocchi resource type. type CreateResult struct { commonResult } // UpdateResult represents the result of an update operation. Call its Extract // method to interpret it as a Gnocchi resource type. type UpdateResult struct { commonResult } // DeleteResult represents the result of a delete operation. Call its // ExtractErr method to determine if the request succeeded or failed. type DeleteResult struct { gophercloud.ErrResult } // ResourceType represents custom Gnocchi resource type. type ResourceType struct { // Attributes is a collection of keys and values of different resource types. Attributes map[string]Attribute `json:"-"` // Name is a human-readable resource type identifier. Name string `json:"name"` // State represents current status of a resource type. State string `json:"state"` } // Attribute represents single attribute of a Gnocchi resource type. type Attribute struct { // Type is an attribute type. Type string `json:"type"` // Details represents different attribute fields. Details map[string]interface{} } // UnmarshalJSON helps to unmarshal ResourceType fields into needed values. func (r *ResourceType) UnmarshalJSON(b []byte) error { type tmp ResourceType var s struct { tmp Attributes map[string]interface{} `json:"attributes"` } err := json.Unmarshal(b, &s) if err != nil { return err } *r = ResourceType(s.tmp) if s.Attributes == nil { return nil } // Populate attributes from the JSON map structure. attributes := make(map[string]Attribute) for attributeName, attributeValues := range s.Attributes { attribute := new(Attribute) attribute.Details = make(map[string]interface{}) attributeValuesMap, ok := attributeValues.(map[string]interface{}) if !ok { // Got some strange resource type attribute representation, skip it. continue } // Populate extra and type attribute values. for k, v := range attributeValuesMap { if k == "type" { if attributeType, ok := v.(string); ok { attribute.Type = attributeType } } else { attribute.Details[k] = v } } attributes[attributeName] = *attribute } r.Attributes = attributes return err } // ResourceTypePage abstracts the raw results of making a List() request against // the Gnocchi API. // // As Gnocchi API may freely alter the response bodies of structures // returned to the client, you may only safely access the data provided through // the ExtractResources call. type ResourceTypePage struct { pagination.SinglePageBase } // IsEmpty checks whether a ResourceTypePage struct is empty. func (r ResourceTypePage) IsEmpty() (bool, error) { is, err := ExtractResourceTypes(r) return len(is) == 0, err } // ExtractResourceTypes interprets the results of a single page from a List() call, // producing a slice of ResourceType structs. func ExtractResourceTypes(r pagination.Page) ([]ResourceType, error) { var s []ResourceType err := (r.(ResourceTypePage)).ExtractInto(&s) if err != nil { return nil, err } return s, err } golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/resourcetypes/testing/000077500000000000000000000000001467426574700321235ustar00rootroot00000000000000doc.go000066400000000000000000000000551467426574700331400ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/resourcetypes/testing// resource types unit tests package testing fixtures.go000066400000000000000000000113211467426574700342420ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/resourcetypes/testingpackage testing import "github.com/gophercloud/utils/gnocchi/metric/v1/resourcetypes" // ResourceTypeListResult represents raw server response from a server to a list call. const ResourceTypeListResult = `[ { "attributes": {}, "name": "generic", "state": "active" }, { "attributes": { "parent_id": { "required": false, "type": "uuid" } }, "name": "identity_project", "state": "active" }, { "attributes": { "host": { "max_length": 128, "min_length": 0, "required": true, "type": "string" } }, "name": "compute_instance", "state": "active" } ]` // ResourceType1 is an expected representation of a first resource from the ResourceTypeListResult. var ResourceType1 = resourcetypes.ResourceType{ Name: "generic", State: "active", Attributes: map[string]resourcetypes.Attribute{}, } // ResourceType2 is an expected representation of a first resource from the ResourceTypeListResult. var ResourceType2 = resourcetypes.ResourceType{ Name: "identity_project", State: "active", Attributes: map[string]resourcetypes.Attribute{ "parent_id": { Type: "uuid", Details: map[string]interface{}{ "required": false, }, }, }, } // ResourceType3 is an expected representation of a first resource from the ResourceTypeListResult. var ResourceType3 = resourcetypes.ResourceType{ Name: "compute_instance", State: "active", Attributes: map[string]resourcetypes.Attribute{ "host": { Type: "string", Details: map[string]interface{}{ "max_length": float64(128), "min_length": float64(0), "required": true, }, }, }, } // ResourceTypeGetResult represents raw server response from a server to a get request. const ResourceTypeGetResult = ` { "attributes": { "host": { "min_length": 0, "max_length": 255, "type": "string", "required": true }, "image_ref": { "type": "uuid", "required": false } }, "state": "active", "name": "compute_instance" } ` // ResourceTypeCreateWithoutAttributesRequest represents a request to create a resource type without attributes. const ResourceTypeCreateWithoutAttributesRequest = ` { "name":"identity_project" } ` // ResourceTypeCreateWithoutAttributesResult represents a raw server response to the ResourceTypeCreateWithoutAttributesRequest. const ResourceTypeCreateWithoutAttributesResult = ` { "attributes": {}, "state": "active", "name": "identity_project" } ` // ResourceTypeCreateWithAttributesRequest represents a request to create a resource type with attributes. const ResourceTypeCreateWithAttributesRequest = ` { "attributes": { "port_name": { "max_length": 128, "required": false, "type": "string" }, "port_id": { "required": true, "type": "uuid" } }, "name": "compute_instance_network" } ` // ResourceTypeCreateWithAttributesResult represents a raw server response to the ResourceTypeCreateWithAttributesRequest. const ResourceTypeCreateWithAttributesResult = ` { "attributes": { "port_id": { "required": true, "type": "uuid" }, "port_name": { "min_length": 0, "max_length": 128, "type": "string", "required": false } }, "state": "active", "name": "compute_instance_network" } ` // ResourceTypeUpdateRequest represents a request to update a resource type. const ResourceTypeUpdateRequest = ` [ { "op": "add", "path": "/attributes/enabled", "value": { "options": { "fill": true }, "required": true, "type": "bool" } }, { "op": "add", "path": "/attributes/parent_id", "value": { "required": false, "type": "uuid" } }, { "op": "remove", "path": "/attributes/domain_id" } ] ` // ResourceTypeUpdateResult represents a raw server response to the ResourceTypeUpdateRequest. const ResourceTypeUpdateResult = ` { "attributes": { "enabled": { "required": true, "type": "bool" }, "parent_id": { "type": "uuid", "required": false }, "name": { "required": true, "type": "string", "min_length": 0, "max_length": 128 } }, "state": "active", "name": "identity_project" } ` requests_test.go000066400000000000000000000154511467426574700353130ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/resourcetypes/testingpackage testing import ( "fmt" "net/http" "testing" "github.com/gophercloud/gophercloud/pagination" th "github.com/gophercloud/gophercloud/testhelper" "github.com/gophercloud/utils/gnocchi/metric/v1/resourcetypes" fake "github.com/gophercloud/utils/gnocchi/testhelper/client" ) func TestList(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() th.Mux.HandleFunc("/v1/resource_type", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) fmt.Fprintf(w, ResourceTypeListResult) }) count := 0 resourcetypes.List(fake.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) { count++ actual, err := resourcetypes.ExtractResourceTypes(page) if err != nil { t.Errorf("Failed to extract resource types: %v", err) return false, nil } expected := []resourcetypes.ResourceType{ ResourceType1, ResourceType2, ResourceType3, } th.CheckDeepEquals(t, expected, actual) return true, nil }) if count != 1 { t.Errorf("Expected 1 page, got %d", count) } } func TestGet(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() th.Mux.HandleFunc("/v1/resource_type/compute_instance", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) fmt.Fprintf(w, ResourceTypeGetResult) }) s, err := resourcetypes.Get(fake.ServiceClient(), "compute_instance").Extract() th.AssertNoErr(t, err) th.AssertEquals(t, s.Name, "compute_instance") th.AssertEquals(t, s.State, "active") th.AssertDeepEquals(t, s.Attributes, map[string]resourcetypes.Attribute{ "host": { Type: "string", Details: map[string]interface{}{ "max_length": float64(255), "min_length": float64(0), "required": true, }, }, "image_ref": { Type: "uuid", Details: map[string]interface{}{ "required": false, }, }, }) } func TestCreateWithoutAttributes(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() th.Mux.HandleFunc("/v1/resource_type", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ResourceTypeCreateWithoutAttributesRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) fmt.Fprintf(w, ResourceTypeCreateWithoutAttributesResult) }) opts := resourcetypes.CreateOpts{ Name: "identity_project", } s, err := resourcetypes.Create(fake.ServiceClient(), opts).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, s.Name, "identity_project") th.AssertEquals(t, s.State, "active") th.AssertDeepEquals(t, s.Attributes, map[string]resourcetypes.Attribute{}) } func TestCreateWithAttributes(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() th.Mux.HandleFunc("/v1/resource_type", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ResourceTypeCreateWithAttributesRequest) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) fmt.Fprintf(w, ResourceTypeCreateWithAttributesResult) }) opts := resourcetypes.CreateOpts{ Name: "compute_instance_network", Attributes: map[string]resourcetypes.AttributeOpts{ "port_name": { Type: "string", Details: map[string]interface{}{ "max_length": 128, "required": false, }, }, "port_id": { Type: "uuid", Details: map[string]interface{}{ "required": true, }, }, }, } s, err := resourcetypes.Create(fake.ServiceClient(), opts).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, s.Name, "compute_instance_network") th.AssertEquals(t, s.State, "active") th.AssertDeepEquals(t, s.Attributes, map[string]resourcetypes.Attribute{ "port_name": { Type: "string", Details: map[string]interface{}{ "max_length": float64(128), "min_length": float64(0), "required": false, }, }, "port_id": { Type: "uuid", Details: map[string]interface{}{ "required": true, }, }, }) } func TestUpdate(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() th.Mux.HandleFunc("/v1/resource_type/identity_project", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "PATCH") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json-patch+json") th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ResourceTypeUpdateRequest) w.Header().Add("Content-Type", "application/json-patch+json") w.WriteHeader(http.StatusOK) fmt.Fprintf(w, ResourceTypeUpdateResult) }) enabledAttributeOptions := resourcetypes.AttributeOpts{ Details: map[string]interface{}{ "required": true, "options": map[string]interface{}{ "fill": true, }, }, Type: "bool", } parendIDAttributeOptions := resourcetypes.AttributeOpts{ Details: map[string]interface{}{ "required": false, }, Type: "uuid", } opts := resourcetypes.UpdateOpts{ Attributes: []resourcetypes.AttributeUpdateOpts{ { Name: "enabled", Operation: resourcetypes.AttributeAdd, Value: &enabledAttributeOptions, }, { Name: "parent_id", Operation: resourcetypes.AttributeAdd, Value: &parendIDAttributeOptions, }, { Name: "domain_id", Operation: resourcetypes.AttributeRemove, }, }, } s, err := resourcetypes.Update(fake.ServiceClient(), "identity_project", opts).Extract() th.AssertNoErr(t, err) th.AssertEquals(t, s.Name, "identity_project") th.AssertEquals(t, s.State, "active") th.AssertDeepEquals(t, s.Attributes, map[string]resourcetypes.Attribute{ "enabled": { Type: "bool", Details: map[string]interface{}{ "required": true, }, }, "parent_id": { Type: "uuid", Details: map[string]interface{}{ "required": false, }, }, "name": { Type: "string", Details: map[string]interface{}{ "required": true, "min_length": float64(0), "max_length": float64(128), }, }, }) } func TestDelete(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() th.Mux.HandleFunc("/v1/resource_type/compute_instance_network", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusNoContent) }) res := resourcetypes.Delete(fake.ServiceClient(), "compute_instance_network") th.AssertNoErr(t, res.Err) } golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/resourcetypes/urls.go000066400000000000000000000015201467426574700317600ustar00rootroot00000000000000package resourcetypes import "github.com/gophercloud/gophercloud" const resourcePath = "resource_type" func rootURL(c *gophercloud.ServiceClient) string { return c.ServiceURL(resourcePath) } func resourceURL(c *gophercloud.ServiceClient, resourceTypeName string) string { return c.ServiceURL(resourcePath, resourceTypeName) } func listURL(c *gophercloud.ServiceClient) string { return rootURL(c) } func getURL(c *gophercloud.ServiceClient, resourceTypeName string) string { return resourceURL(c, resourceTypeName) } func createURL(c *gophercloud.ServiceClient) string { return rootURL(c) } func updateURL(c *gophercloud.ServiceClient, resourceTypeName string) string { return resourceURL(c, resourceTypeName) } func deleteURL(c *gophercloud.ServiceClient, resourceTypeName string) string { return resourceURL(c, resourceTypeName) } golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/status/000077500000000000000000000000001467426574700270555ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/status/doc.go000066400000000000000000000005231467426574700301510ustar00rootroot00000000000000/* Package status provides the ability to retrieve Gnocchi status through the Gnocchi API. Example of Getting status details := true getOpts := status.GetOpts{ Details: &details, } gnocchiStatus, err := status.Get(client, getOpts).Extract() if err != nil { panic(err) } fmt.Printf("%+v\n", gnocchiStatus) */ package status golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/status/requests.go000066400000000000000000000016301467426574700312570ustar00rootroot00000000000000package status import ( "github.com/gophercloud/gophercloud" ) // GetOptsBuilder allows to add additional parameters to the Get request. type GetOptsBuilder interface { ToStatusGetQuery() (string, error) } // GetOpts allows to provide additional options to the Gnocchi status Get request. type GetOpts struct { // Details allows to get status with all attributes. Details *bool `q:"details"` } // ToStatusGetQuery formats a GetOpts into a query string. func (opts GetOpts) ToStatusGetQuery() (string, error) { q, err := gophercloud.BuildQueryString(opts) return q.String(), err } // Get retrieves the overall status of the Gnocchi installation. func Get(c *gophercloud.ServiceClient, opts GetOptsBuilder) (r GetResult) { url := getURL(c) if opts != nil { query, err := opts.ToStatusGetQuery() if err != nil { r.Err = err return } url += query } _, r.Err = c.Get(url, &r.Body, nil) return } golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/status/results.go000066400000000000000000000030461467426574700311100ustar00rootroot00000000000000package status import "github.com/gophercloud/gophercloud" type commonResult struct { gophercloud.Result } // Extract is a function that accepts a result and extracts a Gnocchi status. func (r commonResult) Extract() (*Status, error) { var s *Status err := r.ExtractInto(&s) return s, err } // GetResult represents the result of a get operation. Call its Extract // method to interpret it as a Gnocchi status. type GetResult struct { commonResult } // Status represents a Gnocchi status of measurements processing. type Status struct { // Metricd represents all running Gnocchi metricd daemons. Metricd Metricd `json:"metricd"` // Storage contains Gnocchi storage data of measures backlog. Storage Storage `json:"storage"` } // Metricd represents all running Gnocchi metricd daemons. type Metricd struct { // Processors represents a list of running Gnocchi metricd processors. Processors []string `json:"processors"` } // Storage contains Gnocchi storage data of metrics and measures to process. type Storage struct { // MeasuresToProcess represents all metrics having measures to process. MeasuresToProcess map[string]int `json:"measures_to_process"` // Summary represents total count of metrics and processing measures. Summary Summary `json:"summary"` } // Summary contains total numbers of metrics and measures to process. type Summary struct { // Measures represents total number of measures to process. Measures int `json:"measures"` // Metrics represents total number of metric having measures to process. Metrics int `json:"metrics"` } golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/status/testing/000077500000000000000000000000001467426574700305325ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/status/testing/doc.go000066400000000000000000000000451467426574700316250ustar00rootroot00000000000000// status unit tests package testing golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/status/testing/fixtures.go000066400000000000000000000053651467426574700327430ustar00rootroot00000000000000package testing import "github.com/gophercloud/utils/gnocchi/metric/v1/status" // StatusGetWithDetailsResult represents raw server response with all status attributes // from a server to a get request. const StatusGetWithDetailsResult = ` { "metricd": { "processors": [ "node-stat1.27.ce1da3c9-6c8c-490d-b256-3ba1e2bceb7b", "node-stat1.10.9d9a99b2-f0ac-496b-36f3-115b84304a84", "node-stat1.23.915e39c0-4002-489c-87bd-9055033d440c" ] }, "storage": { "measures_to_process": { "002583fd-edc7-47c1-a253-b0575b7ebfe8": 1, "0028fe48-c7a9-4515-91ac-c17b4eade4d4": 1, "002d65fe-56bc-4f52-8c71-da87cd82c436": 23, "003ead5d-657a-4ac0-be0d-d0ab3be9590e": 4, "009c537v-58f4-4dc6-ba9c-e931d3e57565": 1, "009cc1eg-2337-4171-930e-75cd54bf6de1": 2 }, "summary": { "measures": 32, "metrics": 6 } } } ` // StatusGetWithoutDetailsResult represents raw server response without details // from a server to a get request. const StatusGetWithoutDetailsResult = ` { "metricd": { "processors": [ "node-stat1.27.ce1da3c9-6c8c-490d-b256-3ba1e2bceb7b", "node-stat1.10.9d9a99b2-f0ac-496b-36f3-115b84304a84", "node-stat1.23.915e39c0-4002-489c-87bd-9055033d440c" ] }, "storage": { "summary": { "measures": 32, "metrics": 6 } } } ` // GetStatusWithDetailsExpected represents an expected response with all status // attributes from a get request. var GetStatusWithDetailsExpected = status.Status{ Metricd: status.Metricd{ Processors: []string{ "node-stat1.27.ce1da3c9-6c8c-490d-b256-3ba1e2bceb7b", "node-stat1.10.9d9a99b2-f0ac-496b-36f3-115b84304a84", "node-stat1.23.915e39c0-4002-489c-87bd-9055033d440c", }, }, Storage: status.Storage{ MeasuresToProcess: map[string]int{ "002583fd-edc7-47c1-a253-b0575b7ebfe8": 1, "0028fe48-c7a9-4515-91ac-c17b4eade4d4": 1, "002d65fe-56bc-4f52-8c71-da87cd82c436": 23, "003ead5d-657a-4ac0-be0d-d0ab3be9590e": 4, "009c537v-58f4-4dc6-ba9c-e931d3e57565": 1, "009cc1eg-2337-4171-930e-75cd54bf6de1": 2, }, Summary: status.Summary{ Measures: 32, Metrics: 6, }, }, } // GetStatusWithoutDetailsExpected represents an expected response without details // from a get request. var GetStatusWithoutDetailsExpected = status.Status{ Metricd: status.Metricd{ Processors: []string{ "node-stat1.27.ce1da3c9-6c8c-490d-b256-3ba1e2bceb7b", "node-stat1.10.9d9a99b2-f0ac-496b-36f3-115b84304a84", "node-stat1.23.915e39c0-4002-489c-87bd-9055033d440c", }, }, Storage: status.Storage{ Summary: status.Summary{ Measures: 32, Metrics: 6, }, }, } requests_test.go000066400000000000000000000031201467426574700337100ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/status/testingpackage testing import ( "fmt" "net/http" "testing" th "github.com/gophercloud/gophercloud/testhelper" "github.com/gophercloud/utils/gnocchi/metric/v1/status" fake "github.com/gophercloud/utils/gnocchi/testhelper/client" ) func TestGetWithDetails(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() th.Mux.HandleFunc("/v1/status", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) fmt.Fprintf(w, StatusGetWithDetailsResult) }) details := true getOpts := status.GetOpts{ Details: &details, } s, err := status.Get(fake.ServiceClient(), getOpts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, s.Metricd, GetStatusWithDetailsExpected.Metricd) th.AssertDeepEquals(t, s.Storage, GetStatusWithDetailsExpected.Storage) } func TestGetWithoutDetails(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() th.Mux.HandleFunc("/v1/status", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) fmt.Fprintf(w, StatusGetWithoutDetailsResult) }) details := false getOpts := status.GetOpts{ Details: &details, } s, err := status.Get(fake.ServiceClient(), getOpts).Extract() th.AssertNoErr(t, err) th.AssertDeepEquals(t, s.Metricd, GetStatusWithoutDetailsExpected.Metricd) th.AssertDeepEquals(t, s.Storage, GetStatusWithoutDetailsExpected.Storage) } golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/metric/v1/status/urls.go000066400000000000000000000003761467426574700303770ustar00rootroot00000000000000package status import "github.com/gophercloud/gophercloud" const resourcePath = "status" func rootURL(c *gophercloud.ServiceClient) string { return c.ServiceURL(resourcePath) } func getURL(c *gophercloud.ServiceClient) string { return rootURL(c) } golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/results.go000066400000000000000000000017411467426574700257540ustar00rootroot00000000000000package gnocchi import ( "bytes" "encoding/json" "time" ) // RFC3339NanoTimezone describes a common timestamp format used by Gnocchi API responses. const RFC3339NanoTimezone = "2006-01-02T15:04:05.999999+00:00" // RFC3339NanoNoTimezone describes a common timestamp format that can be used for Gnocchi requests // with time ranges. const RFC3339NanoNoTimezone = "2006-01-02T15:04:05.999999" // JSONRFC3339NanoTimezone is a type for Gnocchi responses timestamps with a timezone offset. type JSONRFC3339NanoTimezone time.Time // UnmarshalJSON helps to unmarshal timestamps from Gnocchi responses to the // JSONRFC3339NanoTimezone type. func (jt *JSONRFC3339NanoTimezone) UnmarshalJSON(data []byte) error { b := bytes.NewBuffer(data) dec := json.NewDecoder(b) var s string if err := dec.Decode(&s); err != nil { return err } if s == "" { return nil } t, err := time.Parse(RFC3339NanoTimezone, s) if err != nil { return err } *jt = JSONRFC3339NanoTimezone(t) return nil } golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/testhelper/000077500000000000000000000000001467426574700261005ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/testhelper/client/000077500000000000000000000000001467426574700273565ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/gnocchi/testhelper/client/fake.go000066400000000000000000000006171467426574700306170ustar00rootroot00000000000000package client import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/testhelper/client" ) // TokenID is a fake Identity service token. const TokenID = client.TokenID // ServiceClient returns a generic service client for use in tests. func ServiceClient() *gophercloud.ServiceClient { sc := client.ServiceClient() sc.ResourceBase = sc.Endpoint + "v1/" return sc } golang-github-gophercloud-utils-0.0~git20231010.80377ec/go.mod000066400000000000000000000003761467426574700234230ustar00rootroot00000000000000module github.com/gophercloud/utils require ( github.com/gophercloud/gophercloud v1.3.0 github.com/hashicorp/go-uuid v1.0.3 github.com/mitchellh/go-homedir v1.1.0 golang.org/x/sys v0.2.0 golang.org/x/text v0.4.0 gopkg.in/yaml.v2 v2.4.0 ) go 1.15 golang-github-gophercloud-utils-0.0~git20231010.80377ec/go.sum000066400000000000000000000101171467426574700234420ustar00rootroot00000000000000github.com/gophercloud/gophercloud v1.3.0 h1:RUKyCMiZoQR3VlVR5E3K7PK1AC3/qppsWYo6dtBiqs8= github.com/gophercloud/gophercloud v1.3.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= golang-github-gophercloud-utils-0.0~git20231010.80377ec/internal/000077500000000000000000000000001467426574700241235ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/internal/pkg.go000066400000000000000000000000211467426574700252240ustar00rootroot00000000000000package internal golang-github-gophercloud-utils-0.0~git20231010.80377ec/internal/testing/000077500000000000000000000000001467426574700256005ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/internal/testing/pkg.go000066400000000000000000000000201467426574700267000ustar00rootroot00000000000000package testing golang-github-gophercloud-utils-0.0~git20231010.80377ec/internal/testing/util_test.go000066400000000000000000000016051467426574700301450ustar00rootroot00000000000000package testing import ( "reflect" "testing" "github.com/gophercloud/utils/internal" ) func TestRemainingKeys(t *testing.T) { type User struct { UserID string `json:"user_id"` Username string `json:"username"` Location string `json:"-"` CreatedAt string `json:"-"` Status string IsAdmin bool } userResponse := map[string]interface{}{ "user_id": "abcd1234", "username": "jdoe", "location": "Hawaii", "created_at": "2017-06-08T02:49:03.000000", "status": "active", "is_admin": "true", "custom_field": "foo", } expected := map[string]interface{}{ "created_at": "2017-06-08T02:49:03.000000", "is_admin": "true", "custom_field": "foo", } actual := internal.RemainingKeys(User{}, userResponse) isEqual := reflect.DeepEqual(expected, actual) if !isEqual { t.Fatalf("expected %s but got %s", expected, actual) } } golang-github-gophercloud-utils-0.0~git20231010.80377ec/internal/util.go000066400000000000000000000052301467426574700254270ustar00rootroot00000000000000package internal import ( "bytes" "crypto/tls" "crypto/x509" "fmt" "io/ioutil" "os" "reflect" "strings" "github.com/mitchellh/go-homedir" ) // RemainingKeys will inspect a struct and compare it to a map. Any struct // field that does not have a JSON tag that matches a key in the map or // a matching lower-case field in the map will be returned as an extra. // // This is useful for determining the extra fields returned in response bodies // for resources that can contain an arbitrary or dynamic number of fields. func RemainingKeys(s interface{}, m map[string]interface{}) (extras map[string]interface{}) { extras = make(map[string]interface{}) for k, v := range m { extras[k] = v } valueOf := reflect.ValueOf(s) typeOf := reflect.TypeOf(s) for i := 0; i < valueOf.NumField(); i++ { field := typeOf.Field(i) lowerField := strings.ToLower(field.Name) delete(extras, lowerField) if tagValue := field.Tag.Get("json"); tagValue != "" && tagValue != "-" { delete(extras, tagValue) } } return } // PrepareTLSConfig generates TLS config based on the specifed parameters func PrepareTLSConfig(caCertFile, clientCertFile, clientKeyFile string, insecure *bool) (*tls.Config, error) { config := &tls.Config{} if caCertFile != "" { caCert, _, err := pathOrContents(caCertFile) if err != nil { return nil, fmt.Errorf("Error reading CA Cert: %s", err) } caCertPool := x509.NewCertPool() if ok := caCertPool.AppendCertsFromPEM(bytes.TrimSpace(caCert)); !ok { return nil, fmt.Errorf("Error parsing CA Cert from %s", caCertFile) } config.RootCAs = caCertPool } if insecure == nil { config.InsecureSkipVerify = false } else { config.InsecureSkipVerify = *insecure } if clientCertFile != "" && clientKeyFile != "" { clientCert, _, err := pathOrContents(clientCertFile) if err != nil { return nil, fmt.Errorf("Error reading Client Cert: %s", err) } clientKey, _, err := pathOrContents(clientKeyFile) if err != nil { return nil, fmt.Errorf("Error reading Client Key: %s", err) } cert, err := tls.X509KeyPair(clientCert, clientKey) if err != nil { return nil, err } config.Certificates = []tls.Certificate{cert} config.BuildNameToCertificate() } return config, nil } func pathOrContents(poc string) ([]byte, bool, error) { if len(poc) == 0 { return nil, false, nil } path := poc if path[0] == '~' { var err error path, err = homedir.Expand(path) if err != nil { return []byte(path), true, err } } if _, err := os.Stat(path); err == nil { contents, err := ioutil.ReadFile(path) if err != nil { return contents, true, err } return contents, true, nil } return []byte(poc), false, nil } golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/000077500000000000000000000000001467426574700242765ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/baremetal/000077500000000000000000000000001467426574700262325ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/baremetal/v1/000077500000000000000000000000001467426574700265605ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/baremetal/v1/nodes/000077500000000000000000000000001467426574700276705ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/baremetal/v1/nodes/configdrive.go000066400000000000000000000060271467426574700325230ustar00rootroot00000000000000package nodes import ( "encoding/base64" "encoding/json" "io/ioutil" "os" "path/filepath" ) // A ConfigDrive struct will be used to create a base64-encoded, gzipped ISO9660 image for use with Ironic. type ConfigDrive struct { UserData UserDataBuilder `json:"user_data"` MetaData map[string]interface{} `json:"meta_data"` NetworkData map[string]interface{} `json:"network_data"` Version string `json:"-"` BuildDirectory string `json:"-"` } // Interface to let us specify a raw string, or a map for the user data type UserDataBuilder interface { ToUserData() ([]byte, error) } type UserDataMap map[string]interface{} type UserDataString string // Converts a UserDataMap to JSON-string func (data UserDataMap) ToUserData() ([]byte, error) { return json.MarshalIndent(data, "", "\t") } func (data UserDataString) ToUserData() ([]byte, error) { return []byte(data), nil } type ConfigDriveBuilder interface { ToConfigDrive() (string, error) } // Writes out a ConfigDrive to a temporary directory, and returns the path func (configDrive ConfigDrive) ToDirectory() (string, error) { // Create a temporary directory for our config drive directory, err := ioutil.TempDir(configDrive.BuildDirectory, "gophercloud") if err != nil { return "", err } // Build up the paths for OpenStack var version string if configDrive.Version == "" { version = "latest" } else { version = configDrive.Version } path := filepath.FromSlash(directory + "/openstack/" + version) if err := os.MkdirAll(path, 0755); err != nil { return "", err } // Dump out user data if configDrive.UserData != nil { userDataPath := filepath.FromSlash(path + "/user_data") data, err := configDrive.UserData.ToUserData() if err != nil { return "", err } if err := ioutil.WriteFile(userDataPath, data, 0644); err != nil { return "", err } } // Dump out meta data if configDrive.MetaData != nil { metaDataPath := filepath.FromSlash(path + "/meta_data.json") data, err := json.Marshal(configDrive.MetaData) if err != nil { return "", err } if err := ioutil.WriteFile(metaDataPath, data, 0644); err != nil { return "", err } } // Dump out network data if configDrive.NetworkData != nil { networkDataPath := filepath.FromSlash(path + "/network_data.json") data, err := json.Marshal(configDrive.NetworkData) if err != nil { return "", err } if err := ioutil.WriteFile(networkDataPath, data, 0644); err != nil { return "", err } } return directory, nil } // Writes out the ConfigDrive struct to a directory structure, and then // packs it as a base64-encoded gzipped ISO9660 image. func (configDrive ConfigDrive) ToConfigDrive() (string, error) { directory, err := configDrive.ToDirectory() if err != nil { return "", err } defer os.RemoveAll(directory) // Pack result as gzipped ISO9660 file result, err := PackDirectoryAsISO(directory) if err != nil { return "", err } // Return as base64-encoded data return base64.StdEncoding.EncodeToString(result), nil } golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/baremetal/v1/nodes/doc.go000066400000000000000000000015071467426574700307670ustar00rootroot00000000000000package nodes /* Package nodes provides utilities for working with Ironic's baremetal API. * Building a config drive As part of provisioning a node, you may need a config drive that contains user data, metadata, and network data stored inside a base64-encoded gzipped ISO9660 file. These utilities can create that for you. For example: configDrive = nodes.ConfigDrive{ UserData: nodes.UserDataMap{ "ignition": map[string]string{ "version": "2.2.0", }, "systemd": map[string]interface{}{ "units": []map[string]interface{}{{ "name": "example.service", "enabled": true, }, }, }, } Then to upload this to Ironic as a using gophercloud: err = nodes.ChangeProvisionState(client, uuid, nodes.ProvisionStateOpts{ Target: "active", ConfigDrive: configDrive.ToConfigDrive(), }).ExtractErr() */ golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/baremetal/v1/nodes/testing/000077500000000000000000000000001467426574700313455ustar00rootroot00000000000000configdrive_test.go000066400000000000000000000037671467426574700351700ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/baremetal/v1/nodes/testingpackage testing import ( "io/ioutil" "os" "path/filepath" "testing" th "github.com/gophercloud/gophercloud/testhelper" "github.com/gophercloud/utils/openstack/baremetal/v1/nodes" ) func TestUserDataFromMap(t *testing.T) { userData, err := IgnitionUserData.ToUserData() th.AssertNoErr(t, err) th.CheckJSONEquals(t, string(userData), IgnitionUserData) } func TestUserDataFromString(t *testing.T) { cloudInit := nodes.UserDataString(CloudInitString) userData, err := cloudInit.ToUserData() th.AssertNoErr(t, err) th.AssertByteArrayEquals(t, userData, []byte(cloudInit)) } func TestConfigDriveToDirectory(t *testing.T) { path, err := ConfigDrive.ToDirectory() th.AssertNoErr(t, err) defer os.RemoveAll(path) basePath := filepath.FromSlash(path + "/openstack/latest") userData, err := ioutil.ReadFile(filepath.FromSlash(basePath + "/user_data")) th.AssertNoErr(t, err) th.CheckJSONEquals(t, string(userData), IgnitionUserData) metaData, err := ioutil.ReadFile(filepath.FromSlash(basePath + "/meta_data.json")) th.AssertNoErr(t, err) th.CheckJSONEquals(t, string(metaData), OpenStackMetaData) networkData, err := ioutil.ReadFile(filepath.FromSlash(basePath + "/network_data.json")) th.AssertNoErr(t, err) th.CheckJSONEquals(t, string(networkData), NetworkData) } func TestConfigDriveVersionToDirectory(t *testing.T) { path, err := ConfigDriveVersioned.ToDirectory() th.AssertNoErr(t, err) defer os.RemoveAll(path) basePath := filepath.FromSlash(path + "/openstack/" + ConfigDriveVersioned.Version) userData, err := ioutil.ReadFile(filepath.FromSlash(basePath + "/user_data")) th.AssertNoErr(t, err) th.CheckJSONEquals(t, string(userData), IgnitionUserData) metaData, err := ioutil.ReadFile(filepath.FromSlash(basePath + "/meta_data.json")) th.AssertNoErr(t, err) th.CheckJSONEquals(t, string(metaData), OpenStackMetaData) networkData, err := ioutil.ReadFile(filepath.FromSlash(basePath + "/network_data.json")) th.AssertNoErr(t, err) th.CheckJSONEquals(t, string(networkData), NetworkData) } fixtures.go000066400000000000000000000042361467426574700334730ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/baremetal/v1/nodes/testingpackage testing import "github.com/gophercloud/utils/openstack/baremetal/v1/nodes" const IgnitionConfig = ` { "ignition": { "version": "2.2.0" }, "systemd": { "units": [ { "enabled": true, "name": "example.service" } ] } } ` const OpenstackMetaDataJSON = ` { "availability_zone": "nova", "hostname": "test.novalocal", "public_keys": { "mykey": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDBqUfVvCSez0/Wfpd8dLLgZXV9GtXQ7hnMN+Z0OWQUyebVEHey1CXuin0uY1cAJMhUq8j98SiW+cU0sU4J3x5l2+xi1bodDm1BtFWVeLIOQINpfV1n8fKjHB+ynPpe1F6tMDvrFGUlJs44t30BrujMXBe8Rq44cCk6wqyjATA3rQ== Generated by Nova\n" } } ` const NetworkDataJSON = ` "services": [ { "type": "dns", "address": "8.8.8.8" }, { "type": "dns", "address": "8.8.4.4" } ] ` const CloudInitString = ` #cloud-init groups: - ubuntu: [root,sys] - cloud-users ` var ( IgnitionUserData = nodes.UserDataMap{ "ignition": map[string]string{ "version": "2.2.0", }, "systemd": map[string]interface{}{ "units": []map[string]interface{}{{ "name": "example.service", "enabled": true, }, }, }, } OpenStackMetaData = map[string]interface{}{ "availability_zone": "nova", "hostname": "test.novalocal", "public_keys": map[string]string{ "mykey": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDBqUfVvCSez0/Wfpd8dLLgZXV9GtXQ7hnMN+Z0OWQUyebVEHey1CXuin0uY1cAJMhUq8j98SiW+cU0sU4J3x5l2+xi1bodDm1BtFWVeLIOQINpfV1n8fKjHB+ynPpe1F6tMDvrFGUlJs44t30BrujMXBe8Rq44cCk6wqyjATA3rQ== Generated by Nova\n", }, } NetworkData = map[string]interface{}{ "services": []map[string]string{ { "type": "dns", "address": "8.8.8.8", }, { "type": "dns", "address": "8.8.4.4", }, }, } CloudInitUserData = nodes.UserDataString(CloudInitString) ConfigDrive = nodes.ConfigDrive{ UserData: IgnitionUserData, MetaData: OpenStackMetaData, NetworkData: NetworkData, } ConfigDriveVersioned = nodes.ConfigDrive{ UserData: IgnitionUserData, MetaData: OpenStackMetaData, NetworkData: NetworkData, Version: "2018-10-10", } ) golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/baremetal/v1/nodes/util.go000066400000000000000000000020041467426574700311700ustar00rootroot00000000000000package nodes import ( "bytes" "compress/gzip" "fmt" "io/ioutil" "os" "os/exec" ) // Gzips a file func GzipFile(path string) ([]byte, error) { var buf bytes.Buffer w := gzip.NewWriter(&buf) contents, err := ioutil.ReadFile(path) if err != nil { return nil, err } _, err = w.Write(contents) if err != nil { return nil, err } err = w.Close() if err != nil { return nil, err } result := buf.Bytes() return result, nil } // Packs a directory into a gzipped ISO image func PackDirectoryAsISO(path string) ([]byte, error) { iso, err := ioutil.TempFile("", "gophercloud-iso") if err != nil { return nil, err } iso.Close() defer os.Remove(iso.Name()) cmd := exec.Command( "mkisofs", "-o", iso.Name(), "-ldots", "-allow-lowercase", "-allow-multidot", "-l", "-publisher", "gophercloud", "-quiet", "-J", "-r", "-V", "config-2", path, ) if err = cmd.Run(); err != nil { return nil, fmt.Errorf("error creating configdrive iso: %s", err.Error()) } return GzipFile(iso.Name()) } golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/blockstorage/000077500000000000000000000000001467426574700267555ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/blockstorage/extensions/000077500000000000000000000000001467426574700311545ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/blockstorage/extensions/backups/000077500000000000000000000000001467426574700326045ustar00rootroot00000000000000utils.go000066400000000000000000000023111467426574700342110ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/blockstorage/extensions/backupspackage backups import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups" ) // IDFromName is a convenience function that returns a backup's ID given its // name. Errors when the number of items found is not one. func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { IDs, err := IDsFromName(client, name) if err != nil { return "", err } switch count := len(IDs); count { case 0: return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "backup"} case 1: return IDs[0], nil default: return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "backup"} } } // IDsFromName returns zero or more IDs corresponding to a name. The returned // error is only non-nil in case of failure. func IDsFromName(client *gophercloud.ServiceClient, name string) ([]string, error) { pages, err := backups.List(client, backups.ListOpts{ Name: name, }).AllPages() if err != nil { return nil, err } all, err := backups.ExtractBackups(pages) if err != nil { return nil, err } IDs := make([]string, len(all)) for i := range all { IDs[i] = all[i].ID } return IDs, nil } golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/blockstorage/v1/000077500000000000000000000000001467426574700273035ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/blockstorage/v1/snapshots/000077500000000000000000000000001467426574700313255ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/blockstorage/v1/snapshots/utils.go000066400000000000000000000016011467426574700330120ustar00rootroot00000000000000package snapshots import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/snapshots" ) // IDFromName is a convenience function that returns a snapshot's ID given its name. func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { count := 0 id := "" listOpts := snapshots.ListOpts{ Name: name, } pages, err := snapshots.List(client, listOpts).AllPages() if err != nil { return "", err } all, err := snapshots.ExtractSnapshots(pages) if err != nil { return "", err } for _, s := range all { if s.Name == name { count++ id = s.ID } } switch count { case 0: return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "snapshot"} case 1: return id, nil default: return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "snapshot"} } } golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/blockstorage/v1/volumes/000077500000000000000000000000001467426574700307755ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/blockstorage/v1/volumes/utils.go000066400000000000000000000015571467426574700324740ustar00rootroot00000000000000package volumes import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes" ) // IDFromName is a convenience function that returns a volume's ID given its name. func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { count := 0 id := "" listOpts := volumes.ListOpts{ Name: name, } pages, err := volumes.List(client, listOpts).AllPages() if err != nil { return "", err } all, err := volumes.ExtractVolumes(pages) if err != nil { return "", err } for _, s := range all { if s.Name == name { count++ id = s.ID } } switch count { case 0: return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "volume"} case 1: return id, nil default: return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "volume"} } } golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/blockstorage/v2/000077500000000000000000000000001467426574700273045ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/blockstorage/v2/snapshots/000077500000000000000000000000001467426574700313265ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/blockstorage/v2/snapshots/utils.go000066400000000000000000000016011467426574700330130ustar00rootroot00000000000000package snapshots import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots" ) // IDFromName is a convenience function that returns a snapshot's ID given its name. func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { count := 0 id := "" listOpts := snapshots.ListOpts{ Name: name, } pages, err := snapshots.List(client, listOpts).AllPages() if err != nil { return "", err } all, err := snapshots.ExtractSnapshots(pages) if err != nil { return "", err } for _, s := range all { if s.Name == name { count++ id = s.ID } } switch count { case 0: return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "snapshot"} case 1: return id, nil default: return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "snapshot"} } } golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/blockstorage/v2/volumes/000077500000000000000000000000001467426574700307765ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/blockstorage/v2/volumes/utils.go000066400000000000000000000015571467426574700324750ustar00rootroot00000000000000package volumes import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes" ) // IDFromName is a convenience function that returns a volume's ID given its name. func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { count := 0 id := "" listOpts := volumes.ListOpts{ Name: name, } pages, err := volumes.List(client, listOpts).AllPages() if err != nil { return "", err } all, err := volumes.ExtractVolumes(pages) if err != nil { return "", err } for _, s := range all { if s.Name == name { count++ id = s.ID } } switch count { case 0: return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "volume"} case 1: return id, nil default: return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "volume"} } } golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/blockstorage/v3/000077500000000000000000000000001467426574700273055ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/blockstorage/v3/availabilityzones/000077500000000000000000000000001467426574700330365ustar00rootroot00000000000000utils.go000066400000000000000000000022141467426574700344450ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/blockstorage/v3/availabilityzonespackage availabilityzones import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/availabilityzones" ) // ListAvailableAvailabilityZones is a convenience function that return a slice of available Availability Zones. func ListAvailableAvailabilityZones(client *gophercloud.ServiceClient) ([]string, error) { var zones []string allPages, err := availabilityzones.List(client).AllPages() if err != nil { return nil, err } availabilityZoneInfo, err := availabilityzones.ExtractAvailabilityZones(allPages) if err != nil { return nil, err } // This should always return at at least two AZs. By default, Nova will // return an AZ for internal services (typically called 'internal') and AZ // for (typically called 'nova'). We can obviously configure additional AZs // and you can also configure the names of these default AZs with // '[DEFAULT] internal_service_availability_zone' and // '[DEFAULT] default_availability_zone', respectively. for _, zone := range availabilityZoneInfo { if zone.ZoneState.Available { zones = append(zones, zone.ZoneName) } } return zones, nil } golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/blockstorage/v3/snapshots/000077500000000000000000000000001467426574700313275ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/blockstorage/v3/snapshots/utils.go000066400000000000000000000023231467426574700330160ustar00rootroot00000000000000package snapshots import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/snapshots" ) // IDFromName is a convenience function that returns a snapshot's ID given its // name. Errors when the number of items found is not one. func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { IDs, err := IDsFromName(client, name) if err != nil { return "", err } switch count := len(IDs); count { case 0: return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "snapshot"} case 1: return IDs[0], nil default: return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "snapshot"} } } // IDsFromName returns zero or more IDs corresponding to a name. The returned // error is only non-nil in case of failure. func IDsFromName(client *gophercloud.ServiceClient, name string) ([]string, error) { pages, err := snapshots.List(client, snapshots.ListOpts{ Name: name, }).AllPages() if err != nil { return nil, err } all, err := snapshots.ExtractSnapshots(pages) if err != nil { return nil, err } IDs := make([]string, len(all)) for i := range all { IDs[i] = all[i].ID } return IDs, nil } golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/blockstorage/v3/volumes/000077500000000000000000000000001467426574700307775ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/blockstorage/v3/volumes/utils.go000066400000000000000000000023011467426574700324620ustar00rootroot00000000000000package volumes import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes" ) // IDFromName is a convenience function that returns a volume's ID given its // name. Errors when the number of items found is not one. func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { IDs, err := IDsFromName(client, name) if err != nil { return "", err } switch count := len(IDs); count { case 0: return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "volume"} case 1: return IDs[0], nil default: return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "volume"} } } // IDsFromName returns zero or more IDs corresponding to a name. The returned // error is only non-nil in case of failure. func IDsFromName(client *gophercloud.ServiceClient, name string) ([]string, error) { pages, err := volumes.List(client, volumes.ListOpts{ Name: name, }).AllPages() if err != nil { return nil, err } all, err := volumes.ExtractVolumes(pages) if err != nil { return nil, err } IDs := make([]string, len(all)) for i := range all { IDs[i] = all[i].ID } return IDs, nil } golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/clientconfig/000077500000000000000000000000001467426574700267425ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/clientconfig/doc.go000066400000000000000000000020011467426574700300270ustar00rootroot00000000000000/* Package clientconfig provides convienent functions for creating OpenStack clients. It is based on the Python os-client-config library. See https://docs.openstack.org/os-client-config/latest for details. Example to Create a Provider Client From clouds.yaml opts := &clientconfig.ClientOpts{ Cloud: "hawaii", } pClient, err := clientconfig.AuthenticatedClient(opts) if err != nil { panic(err) } Example to Manually Create a Provider Client opts := &clientconfig.ClientOpts{ AuthInfo: &clientconfig.AuthInfo{ AuthURL: "https://hi.example.com:5000/v3", Username: "jdoe", Password: "password", ProjectName: "Some Project", DomainName: "default", }, } pClient, err := clientconfig.AuthenticatedClient(opts) if err != nil { panic(err) } Example to Create a Service Client from clouds.yaml opts := &clientconfig.ClientOpts{ Cloud: "hawaii", } computeClient, err := clientconfig.NewServiceClient("compute", opts) if err != nil { panic(err) } */ package clientconfig golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/clientconfig/requests.go000066400000000000000000000707541467426574700311610ustar00rootroot00000000000000package clientconfig import ( "errors" "fmt" "net/http" "os" "reflect" "strings" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack" "github.com/gophercloud/utils/env" "github.com/gophercloud/utils/gnocchi" "github.com/gophercloud/utils/internal" "github.com/hashicorp/go-uuid" yaml "gopkg.in/yaml.v2" ) // AuthType respresents a valid method of authentication. type AuthType string const ( // AuthPassword defines an unknown version of the password AuthPassword AuthType = "password" // AuthToken defined an unknown version of the token AuthToken AuthType = "token" // AuthV2Password defines version 2 of the password AuthV2Password AuthType = "v2password" // AuthV2Token defines version 2 of the token AuthV2Token AuthType = "v2token" // AuthV3Password defines version 3 of the password AuthV3Password AuthType = "v3password" // AuthV3Token defines version 3 of the token AuthV3Token AuthType = "v3token" // AuthV3ApplicationCredential defines version 3 of the application credential AuthV3ApplicationCredential AuthType = "v3applicationcredential" ) // ClientOpts represents options to customize the way a client is // configured. type ClientOpts struct { // Cloud is the cloud entry in clouds.yaml to use. Cloud string // EnvPrefix allows a custom environment variable prefix to be used. EnvPrefix string // AuthType specifies the type of authentication to use. // By default, this is "password". AuthType AuthType // AuthInfo defines the authentication information needed to // authenticate to a cloud when clouds.yaml isn't used. AuthInfo *AuthInfo // RegionName is the region to create a Service Client in. // This will override a region in clouds.yaml or can be used // when authenticating directly with AuthInfo. RegionName string // EndpointType specifies whether to use the public, internal, or // admin endpoint of a service. EndpointType string // HTTPClient provides the ability customize the ProviderClient's // internal HTTP client. HTTPClient *http.Client // YAMLOpts provides the ability to pass a customized set // of options and methods for loading the YAML file. // It takes a YAMLOptsBuilder interface that is defined // in this file. This is optional and the default behavior // is to call the local LoadCloudsYAML functions defined // in this file. YAMLOpts YAMLOptsBuilder } // YAMLOptsBuilder defines an interface for customization when // loading a clouds.yaml file. type YAMLOptsBuilder interface { LoadCloudsYAML() (map[string]Cloud, error) LoadSecureCloudsYAML() (map[string]Cloud, error) LoadPublicCloudsYAML() (map[string]Cloud, error) } // YAMLOpts represents options and methods to load a clouds.yaml file. type YAMLOpts struct { // By default, no options are specified. } // LoadCloudsYAML defines how to load a clouds.yaml file. // By default, this calls the local LoadCloudsYAML function. func (opts YAMLOpts) LoadCloudsYAML() (map[string]Cloud, error) { return LoadCloudsYAML() } // LoadSecureCloudsYAML defines how to load a secure.yaml file. // By default, this calls the local LoadSecureCloudsYAML function. func (opts YAMLOpts) LoadSecureCloudsYAML() (map[string]Cloud, error) { return LoadSecureCloudsYAML() } // LoadPublicCloudsYAML defines how to load a public-secure.yaml file. // By default, this calls the local LoadPublicCloudsYAML function. func (opts YAMLOpts) LoadPublicCloudsYAML() (map[string]Cloud, error) { return LoadPublicCloudsYAML() } // LoadCloudsYAML will load a clouds.yaml file and return the full config. // This is called by the YAMLOpts method. Calling this function directly // is supported for now but has only been retained for backwards // compatibility from before YAMLOpts was defined. This may be removed in // the future. func LoadCloudsYAML() (map[string]Cloud, error) { _, content, err := FindAndReadCloudsYAML() if err != nil { return nil, err } var clouds Clouds err = yaml.Unmarshal(content, &clouds) if err != nil { return nil, fmt.Errorf("failed to unmarshal yaml: %w", err) } return clouds.Clouds, nil } // LoadSecureCloudsYAML will load a secure.yaml file and return the full config. // This is called by the YAMLOpts method. Calling this function directly // is supported for now but has only been retained for backwards // compatibility from before YAMLOpts was defined. This may be removed in // the future. func LoadSecureCloudsYAML() (map[string]Cloud, error) { var secureClouds Clouds _, content, err := FindAndReadSecureCloudsYAML() if err != nil { if errors.Is(err, os.ErrNotExist) { // secure.yaml is optional so just ignore read error return secureClouds.Clouds, nil } return nil, err } err = yaml.Unmarshal(content, &secureClouds) if err != nil { return nil, fmt.Errorf("failed to unmarshal yaml: %w", err) } return secureClouds.Clouds, nil } // LoadPublicCloudsYAML will load a public-clouds.yaml file and return the full config. // This is called by the YAMLOpts method. Calling this function directly // is supported for now but has only been retained for backwards // compatibility from before YAMLOpts was defined. This may be removed in // the future. func LoadPublicCloudsYAML() (map[string]Cloud, error) { var publicClouds PublicClouds _, content, err := FindAndReadPublicCloudsYAML() if err != nil { if errors.Is(err, os.ErrNotExist) { // clouds-public.yaml is optional so just ignore read error return publicClouds.Clouds, nil } return nil, err } err = yaml.Unmarshal(content, &publicClouds) if err != nil { return nil, fmt.Errorf("failed to unmarshal yaml: %w", err) } return publicClouds.Clouds, nil } // GetCloudFromYAML will return a cloud entry from a clouds.yaml file. func GetCloudFromYAML(opts *ClientOpts) (*Cloud, error) { if opts == nil { opts = new(ClientOpts) } if opts.YAMLOpts == nil { opts.YAMLOpts = new(YAMLOpts) } yamlOpts := opts.YAMLOpts clouds, err := yamlOpts.LoadCloudsYAML() if err != nil { return nil, fmt.Errorf("unable to load clouds.yaml: %w", err) } // Determine which cloud to use. // First see if a cloud name was explicitly set in opts. var cloudName string if opts.Cloud != "" { cloudName = opts.Cloud } else { // If not, see if a cloud name was specified as an environment variable. envPrefix := "OS_" if opts.EnvPrefix != "" { envPrefix = opts.EnvPrefix } if v := env.Getenv(envPrefix + "CLOUD"); v != "" { cloudName = v } } var cloud *Cloud if cloudName != "" { v, ok := clouds[cloudName] if !ok { return nil, fmt.Errorf("cloud %s does not exist in clouds.yaml", cloudName) } cloud = &v } // If a cloud was not specified, and clouds only contains // a single entry, use that entry. if cloudName == "" && len(clouds) == 1 { for _, v := range clouds { cloud = &v } } if cloud != nil { // A profile points to a public cloud entry. // If one was specified, load a list of public clouds // and then merge the information with the current cloud data. profileName := defaultIfEmpty(cloud.Profile, cloud.Cloud) if profileName != "" { publicClouds, err := yamlOpts.LoadPublicCloudsYAML() if err != nil { return nil, fmt.Errorf("unable to load clouds-public.yaml: %w", err) } publicCloud, ok := publicClouds[profileName] if !ok { return nil, fmt.Errorf("cloud %s does not exist in clouds-public.yaml", profileName) } cloud, err = mergeClouds(cloud, publicCloud) if err != nil { return nil, fmt.Errorf("Could not merge information from clouds.yaml and clouds-public.yaml for cloud %s", profileName) } } } // Next, load a secure clouds file and see if a cloud entry // can be found or merged. secureClouds, err := yamlOpts.LoadSecureCloudsYAML() if err != nil { return nil, fmt.Errorf("unable to load secure.yaml: %w", err) } if secureClouds != nil { // If no entry was found in clouds.yaml, no cloud name was specified, // and only one secureCloud entry exists, use that as the cloud entry. if cloud == nil && cloudName == "" && len(secureClouds) == 1 { for _, v := range secureClouds { cloud = &v } } // Otherwise, see if the provided cloud name exists in the secure yaml file. secureCloud, ok := secureClouds[cloudName] if !ok && cloud == nil { // cloud == nil serves two purposes here: // if no entry in clouds.yaml was found and // if a single-entry secureCloud wasn't used. // At this point, no entry could be determined at all. return nil, fmt.Errorf("Could not find cloud %s", cloudName) } // If secureCloud has content and it differs from the cloud entry, // merge the two together. if !reflect.DeepEqual((Cloud{}), secureCloud) && !reflect.DeepEqual(cloud, secureCloud) { cloud, err = mergeClouds(secureCloud, cloud) if err != nil { return nil, fmt.Errorf("unable to merge information from clouds.yaml and secure.yaml") } } } // As an extra precaution, do one final check to see if cloud is nil. // We shouldn't reach this point, though. if cloud == nil { return nil, fmt.Errorf("Could not find cloud %s", cloudName) } // Default is to verify SSL API requests if cloud.Verify == nil { iTrue := true cloud.Verify = &iTrue } // merging per-region value overrides if opts.RegionName != "" { for _, v := range cloud.Regions { if opts.RegionName == v.Name { cloud, err = mergeClouds(v.Values, cloud) break } } } // TODO: this is where reading vendor files should go be considered when not found in // clouds-public.yml // https://github.com/openstack/openstacksdk/tree/master/openstack/config/vendors // Both Interface and EndpointType are valid settings in clouds.yaml, // but we want to standardize on EndpointType for simplicity. // // If only Interface was set, we copy that to EndpointType to use as the setting. // But in all other cases, EndpointType is used and Interface is cleared. if cloud.Interface != "" && cloud.EndpointType == "" { cloud.EndpointType = cloud.Interface } cloud.Interface = "" return cloud, nil } // AuthOptions creates a gophercloud.AuthOptions structure with the // settings found in a specific cloud entry of a clouds.yaml file or // based on authentication settings given in ClientOpts. // // This attempts to be a single point of entry for all OpenStack authentication. // // See http://docs.openstack.org/developer/os-client-config and // https://github.com/openstack/os-client-config/blob/master/os_client_config/config.py. func AuthOptions(opts *ClientOpts) (*gophercloud.AuthOptions, error) { cloud := new(Cloud) // If no opts were passed in, create an empty ClientOpts. if opts == nil { opts = new(ClientOpts) } // Determine if a clouds.yaml entry should be retrieved. // Start by figuring out the cloud name. // First check if one was explicitly specified in opts. var cloudName string if opts.Cloud != "" { cloudName = opts.Cloud } else { // If not, see if a cloud name was specified as an environment // variable. envPrefix := "OS_" if opts.EnvPrefix != "" { envPrefix = opts.EnvPrefix } if v := env.Getenv(envPrefix + "CLOUD"); v != "" { cloudName = v } } // If a cloud name was determined, try to look it up in clouds.yaml. if cloudName != "" { // Get the requested cloud. var err error cloud, err = GetCloudFromYAML(opts) if err != nil { return nil, err } } // If cloud.AuthInfo is nil, then no cloud was specified. if cloud.AuthInfo == nil { // If opts.AuthInfo is not nil, then try using the auth settings from it. if opts.AuthInfo != nil { cloud.AuthInfo = opts.AuthInfo } // If cloud.AuthInfo is still nil, then set it to an empty Auth struct // and rely on environment variables to do the authentication. if cloud.AuthInfo == nil { cloud.AuthInfo = new(AuthInfo) } } identityAPI := determineIdentityAPI(cloud, opts) switch identityAPI { case "2.0", "2": return v2auth(cloud, opts) case "3": return v3auth(cloud, opts) } return nil, fmt.Errorf("Unable to build AuthOptions") } func determineIdentityAPI(cloud *Cloud, opts *ClientOpts) string { var identityAPI string if cloud.IdentityAPIVersion != "" { identityAPI = cloud.IdentityAPIVersion } envPrefix := "OS_" if opts != nil && opts.EnvPrefix != "" { envPrefix = opts.EnvPrefix } if v := env.Getenv(envPrefix + "IDENTITY_API_VERSION"); v != "" { identityAPI = v } if identityAPI == "" { if cloud.AuthInfo != nil { if strings.Contains(cloud.AuthInfo.AuthURL, "v2.0") { identityAPI = "2.0" } if strings.Contains(cloud.AuthInfo.AuthURL, "v3") { identityAPI = "3" } } } if identityAPI == "" { switch cloud.AuthType { case AuthV2Password: identityAPI = "2.0" case AuthV2Token: identityAPI = "2.0" case AuthV3Password: identityAPI = "3" case AuthV3Token: identityAPI = "3" case AuthV3ApplicationCredential: identityAPI = "3" } } // If an Identity API version could not be determined, // default to v3. if identityAPI == "" { identityAPI = "3" } return identityAPI } // v2auth creates a v2-compatible gophercloud.AuthOptions struct. func v2auth(cloud *Cloud, opts *ClientOpts) (*gophercloud.AuthOptions, error) { // Environment variable overrides. envPrefix := "OS_" if opts != nil && opts.EnvPrefix != "" { envPrefix = opts.EnvPrefix } if cloud.AuthInfo.AuthURL == "" { if v := env.Getenv(envPrefix + "AUTH_URL"); v != "" { cloud.AuthInfo.AuthURL = v } } if cloud.AuthInfo.Token == "" { if v := env.Getenv(envPrefix + "TOKEN"); v != "" { cloud.AuthInfo.Token = v } if v := env.Getenv(envPrefix + "AUTH_TOKEN"); v != "" { cloud.AuthInfo.Token = v } } if cloud.AuthInfo.Username == "" { if v := env.Getenv(envPrefix + "USERNAME"); v != "" { cloud.AuthInfo.Username = v } } if cloud.AuthInfo.Password == "" { if v := env.Getenv(envPrefix + "PASSWORD"); v != "" { cloud.AuthInfo.Password = v } } if cloud.AuthInfo.ProjectID == "" { if v := env.Getenv(envPrefix + "TENANT_ID"); v != "" { cloud.AuthInfo.ProjectID = v } if v := env.Getenv(envPrefix + "PROJECT_ID"); v != "" { cloud.AuthInfo.ProjectID = v } } if cloud.AuthInfo.ProjectName == "" { if v := env.Getenv(envPrefix + "TENANT_NAME"); v != "" { cloud.AuthInfo.ProjectName = v } if v := env.Getenv(envPrefix + "PROJECT_NAME"); v != "" { cloud.AuthInfo.ProjectName = v } } ao := &gophercloud.AuthOptions{ IdentityEndpoint: cloud.AuthInfo.AuthURL, TokenID: cloud.AuthInfo.Token, Username: cloud.AuthInfo.Username, Password: cloud.AuthInfo.Password, TenantID: cloud.AuthInfo.ProjectID, TenantName: cloud.AuthInfo.ProjectName, AllowReauth: cloud.AuthInfo.AllowReauth, } return ao, nil } // v3auth creates a v3-compatible gophercloud.AuthOptions struct. func v3auth(cloud *Cloud, opts *ClientOpts) (*gophercloud.AuthOptions, error) { // Environment variable overrides. envPrefix := "OS_" if opts != nil && opts.EnvPrefix != "" { envPrefix = opts.EnvPrefix } if cloud.AuthInfo.AuthURL == "" { if v := env.Getenv(envPrefix + "AUTH_URL"); v != "" { cloud.AuthInfo.AuthURL = v } } if cloud.AuthInfo.Token == "" { if v := env.Getenv(envPrefix + "TOKEN"); v != "" { cloud.AuthInfo.Token = v } if v := env.Getenv(envPrefix + "AUTH_TOKEN"); v != "" { cloud.AuthInfo.Token = v } } if cloud.AuthInfo.Username == "" { if v := env.Getenv(envPrefix + "USERNAME"); v != "" { cloud.AuthInfo.Username = v } } if cloud.AuthInfo.UserID == "" { if v := env.Getenv(envPrefix + "USER_ID"); v != "" { cloud.AuthInfo.UserID = v } } if cloud.AuthInfo.Password == "" { if v := env.Getenv(envPrefix + "PASSWORD"); v != "" { cloud.AuthInfo.Password = v } } if cloud.AuthInfo.ProjectID == "" { if v := env.Getenv(envPrefix + "TENANT_ID"); v != "" { cloud.AuthInfo.ProjectID = v } if v := env.Getenv(envPrefix + "PROJECT_ID"); v != "" { cloud.AuthInfo.ProjectID = v } } if cloud.AuthInfo.ProjectName == "" { if v := env.Getenv(envPrefix + "TENANT_NAME"); v != "" { cloud.AuthInfo.ProjectName = v } if v := env.Getenv(envPrefix + "PROJECT_NAME"); v != "" { cloud.AuthInfo.ProjectName = v } } if cloud.AuthInfo.DomainID == "" { if v := env.Getenv(envPrefix + "DOMAIN_ID"); v != "" { cloud.AuthInfo.DomainID = v } } if cloud.AuthInfo.DomainName == "" { if v := env.Getenv(envPrefix + "DOMAIN_NAME"); v != "" { cloud.AuthInfo.DomainName = v } } if cloud.AuthInfo.DefaultDomain == "" { if v := env.Getenv(envPrefix + "DEFAULT_DOMAIN"); v != "" { cloud.AuthInfo.DefaultDomain = v } } if cloud.AuthInfo.ProjectDomainID == "" { if v := env.Getenv(envPrefix + "PROJECT_DOMAIN_ID"); v != "" { cloud.AuthInfo.ProjectDomainID = v } } if cloud.AuthInfo.ProjectDomainName == "" { if v := env.Getenv(envPrefix + "PROJECT_DOMAIN_NAME"); v != "" { cloud.AuthInfo.ProjectDomainName = v } } if cloud.AuthInfo.UserDomainID == "" { if v := env.Getenv(envPrefix + "USER_DOMAIN_ID"); v != "" { cloud.AuthInfo.UserDomainID = v } } if cloud.AuthInfo.UserDomainName == "" { if v := env.Getenv(envPrefix + "USER_DOMAIN_NAME"); v != "" { cloud.AuthInfo.UserDomainName = v } } if cloud.AuthInfo.ApplicationCredentialID == "" { if v := env.Getenv(envPrefix + "APPLICATION_CREDENTIAL_ID"); v != "" { cloud.AuthInfo.ApplicationCredentialID = v } } if cloud.AuthInfo.ApplicationCredentialName == "" { if v := env.Getenv(envPrefix + "APPLICATION_CREDENTIAL_NAME"); v != "" { cloud.AuthInfo.ApplicationCredentialName = v } } if cloud.AuthInfo.ApplicationCredentialSecret == "" { if v := env.Getenv(envPrefix + "APPLICATION_CREDENTIAL_SECRET"); v != "" { cloud.AuthInfo.ApplicationCredentialSecret = v } } if cloud.AuthInfo.SystemScope == "" { if v := env.Getenv(envPrefix + "SYSTEM_SCOPE"); v != "" { cloud.AuthInfo.SystemScope = v } } // Build a scope and try to do it correctly. // https://github.com/openstack/os-client-config/blob/master/os_client_config/config.py#L595 scope := new(gophercloud.AuthScope) // Application credentials don't support scope if isApplicationCredential(cloud.AuthInfo) { // If Domain* is set, but UserDomain* or ProjectDomain* aren't, // then use Domain* as the default setting. cloud = setDomainIfNeeded(cloud) } else { if !isProjectScoped(cloud.AuthInfo) { if cloud.AuthInfo.DomainID != "" { scope.DomainID = cloud.AuthInfo.DomainID } else if cloud.AuthInfo.DomainName != "" { scope.DomainName = cloud.AuthInfo.DomainName } if cloud.AuthInfo.SystemScope != "" { scope.System = true } } else { // If Domain* is set, but UserDomain* or ProjectDomain* aren't, // then use Domain* as the default setting. cloud = setDomainIfNeeded(cloud) if cloud.AuthInfo.ProjectID != "" { scope.ProjectID = cloud.AuthInfo.ProjectID } else { scope.ProjectName = cloud.AuthInfo.ProjectName scope.DomainID = cloud.AuthInfo.ProjectDomainID scope.DomainName = cloud.AuthInfo.ProjectDomainName } } } ao := &gophercloud.AuthOptions{ Scope: scope, IdentityEndpoint: cloud.AuthInfo.AuthURL, TokenID: cloud.AuthInfo.Token, Username: cloud.AuthInfo.Username, UserID: cloud.AuthInfo.UserID, Password: cloud.AuthInfo.Password, TenantID: cloud.AuthInfo.ProjectID, TenantName: cloud.AuthInfo.ProjectName, DomainID: cloud.AuthInfo.UserDomainID, DomainName: cloud.AuthInfo.UserDomainName, ApplicationCredentialID: cloud.AuthInfo.ApplicationCredentialID, ApplicationCredentialName: cloud.AuthInfo.ApplicationCredentialName, ApplicationCredentialSecret: cloud.AuthInfo.ApplicationCredentialSecret, AllowReauth: cloud.AuthInfo.AllowReauth, } // If an auth_type of "token" was specified, then make sure // Gophercloud properly authenticates with a token. This involves // unsetting a few other auth options. The reason this is done // here is to wait until all auth settings (both in clouds.yaml // and via environment variables) are set and then unset them. if strings.Contains(string(cloud.AuthType), "token") || ao.TokenID != "" { ao.Username = "" ao.Password = "" ao.UserID = "" ao.DomainID = "" ao.DomainName = "" } // Check for absolute minimum requirements. if ao.IdentityEndpoint == "" { err := gophercloud.ErrMissingInput{Argument: "auth_url"} return nil, err } return ao, nil } // AuthenticatedClient is a convenience function to get a new provider client // based on a clouds.yaml entry. func AuthenticatedClient(opts *ClientOpts) (*gophercloud.ProviderClient, error) { ao, err := AuthOptions(opts) if err != nil { return nil, err } return openstack.AuthenticatedClient(*ao) } // NewServiceClient is a convenience function to get a new service client. func NewServiceClient(service string, opts *ClientOpts) (*gophercloud.ServiceClient, error) { cloud := new(Cloud) // If no opts were passed in, create an empty ClientOpts. if opts == nil { opts = new(ClientOpts) } // Determine if a clouds.yaml entry should be retrieved. // Start by figuring out the cloud name. // First check if one was explicitly specified in opts. var cloudName string if opts.Cloud != "" { cloudName = opts.Cloud } // Next see if a cloud name was specified as an environment variable. envPrefix := "OS_" if opts.EnvPrefix != "" { envPrefix = opts.EnvPrefix } if v := env.Getenv(envPrefix + "CLOUD"); v != "" { cloudName = v } // If a cloud name was determined, try to look it up in clouds.yaml. if cloudName != "" { // Get the requested cloud. var err error cloud, err = GetCloudFromYAML(opts) if err != nil { return nil, err } } // Check if a custom CA cert was provided. // First, check if the CACERT environment variable is set. var caCertPath string if v := env.Getenv(envPrefix + "CACERT"); v != "" { caCertPath = v } // Next, check if the cloud entry sets a CA cert. if v := cloud.CACertFile; v != "" { caCertPath = v } // Check if a custom client cert was provided. // First, check if the CERT environment variable is set. var clientCertPath string if v := env.Getenv(envPrefix + "CERT"); v != "" { clientCertPath = v } // Next, check if the cloud entry sets a client cert. if v := cloud.ClientCertFile; v != "" { clientCertPath = v } // Check if a custom client key was provided. // First, check if the KEY environment variable is set. var clientKeyPath string if v := env.Getenv(envPrefix + "KEY"); v != "" { clientKeyPath = v } // Next, check if the cloud entry sets a client key. if v := cloud.ClientKeyFile; v != "" { clientKeyPath = v } // Define whether or not SSL API requests should be verified. var insecurePtr *bool if cloud.Verify != nil { // Here we take the boolean pointer negation. insecure := !*cloud.Verify insecurePtr = &insecure } tlsConfig, err := internal.PrepareTLSConfig(caCertPath, clientCertPath, clientKeyPath, insecurePtr) if err != nil { return nil, err } // Get a Provider Client ao, err := AuthOptions(opts) if err != nil { return nil, err } pClient, err := openstack.NewClient(ao.IdentityEndpoint) if err != nil { return nil, err } // If an HTTPClient was specified, use it. if opts.HTTPClient != nil { pClient.HTTPClient = *opts.HTTPClient } else { // Otherwise create a new HTTP client with the generated TLS config. transport := http.DefaultTransport.(*http.Transport).Clone() transport.TLSClientConfig = tlsConfig pClient.HTTPClient = http.Client{Transport: transport} } err = openstack.Authenticate(pClient, *ao) if err != nil { return nil, err } // Determine the region to use. // First, check if the REGION_NAME environment variable is set. var region string if v := env.Getenv(envPrefix + "REGION_NAME"); v != "" { region = v } // Next, check if the cloud entry sets a region. if v := cloud.RegionName; v != "" { region = v } // Finally, see if one was specified in the ClientOpts. // If so, this takes precedence. if v := opts.RegionName; v != "" { region = v } // Determine the endpoint type to use. // First, check if the OS_INTERFACE environment variable is set. var endpointType string if v := env.Getenv(envPrefix + "INTERFACE"); v != "" { endpointType = v } // Next, check if the cloud entry sets an endpoint type. if v := cloud.EndpointType; v != "" { endpointType = v } // Finally, see if one was specified in the ClientOpts. // If so, this takes precedence. if v := opts.EndpointType; v != "" { endpointType = v } eo := gophercloud.EndpointOpts{ Region: region, Availability: GetEndpointType(endpointType), } switch service { case "baremetal": return openstack.NewBareMetalV1(pClient, eo) case "baremetal-introspection": return openstack.NewBareMetalIntrospectionV1(pClient, eo) case "clustering": return openstack.NewClusteringV1(pClient, eo) case "compute": return openstack.NewComputeV2(pClient, eo) case "container": return openstack.NewContainerV1(pClient, eo) case "container-infra": return openstack.NewContainerInfraV1(pClient, eo) case "database": return openstack.NewDBV1(pClient, eo) case "dns": return openstack.NewDNSV2(pClient, eo) case "gnocchi": return gnocchi.NewGnocchiV1(pClient, eo) case "identity": identityVersion := "3" if v := cloud.IdentityAPIVersion; v != "" { identityVersion = v } switch identityVersion { case "v2", "2", "2.0": return openstack.NewIdentityV2(pClient, eo) case "v3", "3": return openstack.NewIdentityV3(pClient, eo) default: return nil, fmt.Errorf("invalid identity API version") } case "image": return openstack.NewImageServiceV2(pClient, eo) case "key-manager": return openstack.NewKeyManagerV1(pClient, eo) case "load-balancer": return openstack.NewLoadBalancerV2(pClient, eo) case "messaging": clientID, err := uuid.GenerateUUID() if err != nil { return nil, fmt.Errorf("failed to generate UUID: %w", err) } return openstack.NewMessagingV2(pClient, clientID, eo) case "network": return openstack.NewNetworkV2(pClient, eo) case "object-store": return openstack.NewObjectStorageV1(pClient, eo) case "orchestration": return openstack.NewOrchestrationV1(pClient, eo) case "placement": return openstack.NewPlacementV1(pClient, eo) case "sharev2": return openstack.NewSharedFileSystemV2(pClient, eo) case "volume": volumeVersion := "3" if v := cloud.VolumeAPIVersion; v != "" { volumeVersion = v } switch volumeVersion { case "v1", "1": return openstack.NewBlockStorageV1(pClient, eo) case "v2", "2": return openstack.NewBlockStorageV2(pClient, eo) case "v3", "3": return openstack.NewBlockStorageV3(pClient, eo) default: return nil, fmt.Errorf("invalid volume API version") } case "workflowv2": return openstack.NewWorkflowV2(pClient, eo) } return nil, fmt.Errorf("unable to create a service client for %s", service) } // isProjectScoped determines if an auth struct is project scoped. func isProjectScoped(authInfo *AuthInfo) bool { if authInfo.ProjectID == "" && authInfo.ProjectName == "" { return false } return true } // setDomainIfNeeded will set a DomainID and DomainName // to ProjectDomain* and UserDomain* if not already set. func setDomainIfNeeded(cloud *Cloud) *Cloud { if cloud.AuthInfo.DomainID != "" { if cloud.AuthInfo.UserDomainID == "" { cloud.AuthInfo.UserDomainID = cloud.AuthInfo.DomainID } if cloud.AuthInfo.ProjectDomainID == "" { cloud.AuthInfo.ProjectDomainID = cloud.AuthInfo.DomainID } cloud.AuthInfo.DomainID = "" } if cloud.AuthInfo.DomainName != "" { if cloud.AuthInfo.UserDomainName == "" { cloud.AuthInfo.UserDomainName = cloud.AuthInfo.DomainName } if cloud.AuthInfo.ProjectDomainName == "" { cloud.AuthInfo.ProjectDomainName = cloud.AuthInfo.DomainName } cloud.AuthInfo.DomainName = "" } // If Domain fields are still not set, and if DefaultDomain has a value, // set UserDomainID and ProjectDomainID to DefaultDomain. // https://github.com/openstack/osc-lib/blob/86129e6f88289ef14bfaa3f7c9cdfbea8d9fc944/osc_lib/cli/client_config.py#L117-L146 if cloud.AuthInfo.DefaultDomain != "" { if cloud.AuthInfo.UserDomainName == "" && cloud.AuthInfo.UserDomainID == "" { cloud.AuthInfo.UserDomainID = cloud.AuthInfo.DefaultDomain } if cloud.AuthInfo.ProjectDomainName == "" && cloud.AuthInfo.ProjectDomainID == "" { cloud.AuthInfo.ProjectDomainID = cloud.AuthInfo.DefaultDomain } } return cloud } // isApplicationCredential determines if an application credential is used to auth. func isApplicationCredential(authInfo *AuthInfo) bool { if authInfo.ApplicationCredentialID == "" && authInfo.ApplicationCredentialName == "" && authInfo.ApplicationCredentialSecret == "" { return false } return true } golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/clientconfig/results.go000066400000000000000000000177331467426574700310050ustar00rootroot00000000000000package clientconfig import "encoding/json" // PublicClouds represents a collection of PublicCloud entries in clouds-public.yaml file. // The format of the clouds-public.yml is documented at // https://docs.openstack.org/python-openstackclient/latest/configuration/ type PublicClouds struct { Clouds map[string]Cloud `yaml:"public-clouds" json:"public-clouds"` } // Clouds represents a collection of Cloud entries in a clouds.yaml file. // The format of clouds.yaml is documented at // https://docs.openstack.org/os-client-config/latest/user/configuration.html. type Clouds struct { Clouds map[string]Cloud `yaml:"clouds" json:"clouds"` } // Cloud represents an entry in a clouds.yaml/public-clouds.yaml/secure.yaml file. type Cloud struct { Cloud string `yaml:"cloud,omitempty" json:"cloud,omitempty"` Profile string `yaml:"profile,omitempty" json:"profile,omitempty"` AuthInfo *AuthInfo `yaml:"auth,omitempty" json:"auth,omitempty"` AuthType AuthType `yaml:"auth_type,omitempty" json:"auth_type,omitempty"` RegionName string `yaml:"region_name,omitempty" json:"region_name,omitempty"` Regions []Region `yaml:"regions,omitempty" json:"regions,omitempty"` // EndpointType and Interface both specify whether to use the public, internal, // or admin interface of a service. They should be considered synonymous, but // EndpointType will take precedence when both are specified. EndpointType string `yaml:"endpoint_type,omitempty" json:"endpoint_type,omitempty"` Interface string `yaml:"interface,omitempty" json:"interface,omitempty"` // API Version overrides. IdentityAPIVersion string `yaml:"identity_api_version,omitempty" json:"identity_api_version,omitempty"` VolumeAPIVersion string `yaml:"volume_api_version,omitempty" json:"volume_api_version,omitempty"` // Verify whether or not SSL API requests should be verified. Verify *bool `yaml:"verify,omitempty" json:"verify,omitempty"` // CACertFile a path to a CA Cert bundle that can be used as part of // verifying SSL API requests. CACertFile string `yaml:"cacert,omitempty" json:"cacert,omitempty"` // ClientCertFile a path to a client certificate to use as part of the SSL // transaction. ClientCertFile string `yaml:"cert,omitempty" json:"cert,omitempty"` // ClientKeyFile a path to a client key to use as part of the SSL // transaction. ClientKeyFile string `yaml:"key,omitempty" json:"key,omitempty"` } // AuthInfo represents the auth section of a cloud entry or // auth options entered explicitly in ClientOpts. type AuthInfo struct { // AuthURL is the keystone/identity endpoint URL. AuthURL string `yaml:"auth_url,omitempty" json:"auth_url,omitempty"` // Token is a pre-generated authentication token. Token string `yaml:"token,omitempty" json:"token,omitempty"` // Username is the username of the user. Username string `yaml:"username,omitempty" json:"username,omitempty"` // UserID is the unique ID of a user. UserID string `yaml:"user_id,omitempty" json:"user_id,omitempty"` // Password is the password of the user. Password string `yaml:"password,omitempty" json:"password,omitempty"` // Application Credential ID to login with. ApplicationCredentialID string `yaml:"application_credential_id,omitempty" json:"application_credential_id,omitempty"` // Application Credential name to login with. ApplicationCredentialName string `yaml:"application_credential_name,omitempty" json:"application_credential_name,omitempty"` // Application Credential secret to login with. ApplicationCredentialSecret string `yaml:"application_credential_secret,omitempty" json:"application_credential_secret,omitempty"` // SystemScope is a system information to scope to. SystemScope string `yaml:"system_scope,omitempty" json:"system_scope,omitempty"` // ProjectName is the common/human-readable name of a project. // Users can be scoped to a project. // ProjectName on its own is not enough to ensure a unique scope. It must // also be combined with either a ProjectDomainName or ProjectDomainID. // ProjectName cannot be combined with ProjectID in a scope. ProjectName string `yaml:"project_name,omitempty" json:"project_name,omitempty"` // ProjectID is the unique ID of a project. // It can be used to scope a user to a specific project. ProjectID string `yaml:"project_id,omitempty" json:"project_id,omitempty"` // UserDomainName is the name of the domain where a user resides. // It is used to identify the source domain of a user. UserDomainName string `yaml:"user_domain_name,omitempty" json:"user_domain_name,omitempty"` // UserDomainID is the unique ID of the domain where a user resides. // It is used to identify the source domain of a user. UserDomainID string `yaml:"user_domain_id,omitempty" json:"user_domain_id,omitempty"` // ProjectDomainName is the name of the domain where a project resides. // It is used to identify the source domain of a project. // ProjectDomainName can be used in addition to a ProjectName when scoping // a user to a specific project. ProjectDomainName string `yaml:"project_domain_name,omitempty" json:"project_domain_name,omitempty"` // ProjectDomainID is the name of the domain where a project resides. // It is used to identify the source domain of a project. // ProjectDomainID can be used in addition to a ProjectName when scoping // a user to a specific project. ProjectDomainID string `yaml:"project_domain_id,omitempty" json:"project_domain_id,omitempty"` // DomainName is the name of a domain which can be used to identify the // source domain of either a user or a project. // If UserDomainName and ProjectDomainName are not specified, then DomainName // is used as a default choice. // It can also be used be used to specify a domain-only scope. DomainName string `yaml:"domain_name,omitempty" json:"domain_name,omitempty"` // DomainID is the unique ID of a domain which can be used to identify the // source domain of eitehr a user or a project. // If UserDomainID and ProjectDomainID are not specified, then DomainID is // used as a default choice. // It can also be used be used to specify a domain-only scope. DomainID string `yaml:"domain_id,omitempty" json:"domain_id,omitempty"` // DefaultDomain is the domain ID to fall back on if no other domain has // been specified and a domain is required for scope. DefaultDomain string `yaml:"default_domain,omitempty" json:"default_domain,omitempty"` // AllowReauth should be set to true if you grant permission for Gophercloud to // cache your credentials in memory, and to allow Gophercloud to attempt to // re-authenticate automatically if/when your token expires. If you set it to // false, it will not cache these settings, but re-authentication will not be // possible. This setting defaults to false. AllowReauth bool `yaml:"allow_reauth,omitempty" json:"allow_reauth,omitempty"` } // Region represents a region included as part of cloud in clouds.yaml // According to Python-based openstacksdk, this can be either a struct (as defined) // or a plain string. Custom unmarshallers handle both cases. type Region struct { Name string `yaml:"name,omitempty" json:"name,omitempty"` Values Cloud `yaml:"values,omitempty" json:"values,omitempty"` } // UnmarshalJSON handles either a plain string acting as the Name property or // a struct, mimicking the Python-based openstacksdk. func (r *Region) UnmarshalJSON(data []byte) error { var name string if err := json.Unmarshal(data, &name); err == nil { r.Name = name return nil } type region Region var tmp region if err := json.Unmarshal(data, &tmp); err != nil { return err } r.Name = tmp.Name r.Values = tmp.Values return nil } // UnmarshalYAML handles either a plain string acting as the Name property or // a struct, mimicking the Python-based openstacksdk. func (r *Region) UnmarshalYAML(unmarshal func(interface{}) error) error { var name string if err := unmarshal(&name); err == nil { r.Name = name return nil } type region Region var tmp region if err := unmarshal(&tmp); err != nil { return err } r.Name = tmp.Name r.Values = tmp.Values return nil } golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/clientconfig/testing/000077500000000000000000000000001467426574700304175ustar00rootroot00000000000000clouds-public.yaml000066400000000000000000000001461467426574700337720ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/clientconfig/testingpublic-clouds: rackspace: auth: auth_url: "https://identity.api.rackspacecloud.com/v2.0/" golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/clientconfig/testing/clouds.yaml000066400000000000000000000106021467426574700325730ustar00rootroot00000000000000clouds: hawaii: auth: auth_url: "https://hi.example.com:5000/v3" username: "jdoe" password: "password" project_name: "Some Project" domain_name: "default" region_name: "HNL" florida: interface: "admin" auth: auth_url: "https://fl.example.com:5000/v3" username: "jdoe" password: "password" project_id: "12345" user_domain_id: "abcde" region_name: "MIA" california: interface: "internal" auth: auth_url: "https://ca.example.com:5000/v3" username: "jdoe" password: "password" project_name: "Some Project" project_domain_name: "default" user_domain_name: "default" regions: - SAN - LAX arizona: auth_type: "token" endpoint_type: "public" auth: auth_url: "https://az.example.com:5000/v3" token: "12345" project_name: "Some Project" domain_name: "default" region_name: "PHX" newmexico: auth_type: "password" endpoint_type: "admin" auth: auth_url: "https://nm.example.com:5000/v3" username: "jdoe" password: "password" project_name: "Some Project" project_domain_name: "Some Domain" user_domain_name: "Other Domain" domain_name: "default" region_name: "SAF" nevada: auth_type: "password" interface: "admin" endpoint_type: "internal" auth: auth_url: "https://nv.example.com:5000/v3" user_id: "12345" password: "password" project_name: "Some Project" project_domain_name: "Some Domain" region_name: "LAS" texas: auth: auth_url: "https://tx.example.com:5000/v3" username: "jdoe" password: "password" project_name: "Some Project" user_domain_name: "Some Domain" default_domain: "default" region_name: "AUS" alberta: auth_type: "password" auth: auth_url: "https://ab.example.com:5000/v2.0" username: "jdoe" password: "password" project_name: "Some Project" region_name: "YYC" yukon: auth_type: "v2token" auth: auth_url: "https://yt.example.com:5000/v2.0" token: "12345" project_name: "Some Project" region_name: "YXY" florida_insecure: auth: auth_url: "https://fl.example.com:5000/v3" username: "jdoe" password: "password" project_id: "12345" user_domain_id: "abcde" region_name: "MIA" verify: False florida_secure: auth: auth_url: "https://fl.example.com:5000/v3" username: "jdoe" password: "password" project_id: "12345" user_domain_id: "abcde" region_name: "MIA" key: /home/myhome/client-cert.key cert: /home/myhome/client-cert.crt cacert: /home/myhome/ca.crt chicago: profile: rackspace auth: username: "jdoe" password: "password" project_name: "Some Project" region_name: "ORD" chicago_legacy: cloud: rackspace auth: username: "jdoe" password: "password" project_name: "Some Project" region_name: "ORD" chicago_useprofile: profile: rackspace cloud: rackspace auth: username: "jdoe" password: "password" project_name: "Some Project" region_name: "ORD" philadelphia: auth: auth_url: "https://phl.example.com:5000/v3" username: "jdoe" password: "this should be overwritten by secure.yaml" project_name: "Some Project" region_name: "PHL" philadelphia_complex: auth: auth_url: "https://phl.example.com:5000/v3" username: "jdoe" password: "password" project_name: "Some Project" regions: - name: PHL1 values: auth: auth_url: "https://phl1.example.com:5000/v3" - PHL2 virginia: auth_type: "v3applicationcredential" auth: auth_url: "https://va.example.com:5000/v3" application_credential_id: "app-cred-id" application_credential_secret: "secret" region_name: "VA" disconnected_clouds: auth: username: "jdoe" password: "password" project_name: "Some Project" regions: - name: SOMEWHERE values: auth: auth_url: "https://somewhere.example.com:5000/v3" - name: ANYWHERE values: auth: auth_url: "https://anywhere.example.com:5000/v3" - name: NOWHERE values: auth: auth_url: "https://nowhere.example.com:5000/v3" golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/clientconfig/testing/fixtures.go000066400000000000000000000370451467426574700326300ustar00rootroot00000000000000package testing import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/utils/openstack/clientconfig" ) var iTrue = true var iFalse = false var VirginiaEnvAuth = map[string]string{ "OS_AUTH_URL": "https://va.example.com:5000/v3", "OS_APPLICATION_CREDENTIAL_ID": "app-cred-id", "OS_APPLICATION_CREDENTIAL_SECRET": "secret", } var VirginiaAuthOpts = &gophercloud.AuthOptions{ Scope: &gophercloud.AuthScope{}, IdentityEndpoint: "https://va.example.com:5000/v3", ApplicationCredentialID: "app-cred-id", ApplicationCredentialSecret: "secret", } var VirginiaCloudYAML = clientconfig.Cloud{ RegionName: "VA", AuthInfo: &clientconfig.AuthInfo{ AuthURL: "https://va.example.com:5000/v3", ApplicationCredentialID: "app-cred-id", ApplicationCredentialSecret: "secret", }, Verify: &iTrue, AuthType: "v3applicationcredential", } var PhiladelphiaCloudYAML = clientconfig.Cloud{ RegionName: "PHL", AuthInfo: &clientconfig.AuthInfo{ AuthURL: "https://phl.example.com:5000/v3", Username: "admin", Password: "password", ProjectName: "Some Project", }, Verify: &iTrue, AuthType: "password", } var PhiladelphiaComplexPhl1CloudYAML = clientconfig.Cloud{ AuthInfo: &clientconfig.AuthInfo{ AuthURL: "https://phl1.example.com:5000/v3", Username: "jdoe", Password: "password", ProjectName: "Some Project", }, Regions: []clientconfig.Region{ { Name: "PHL1", Values: clientconfig.Cloud{AuthInfo: &clientconfig.AuthInfo{AuthURL: "https://phl1.example.com:5000/v3"}}, }, { Name: "PHL2", Values: clientconfig.Cloud{}, }, }, Verify: &iTrue, } var PhiladelphiaComplexPhl2CloudYAML = clientconfig.Cloud{ AuthInfo: &clientconfig.AuthInfo{ AuthURL: "https://phl.example.com:5000/v3", Username: "jdoe", Password: "password", ProjectName: "Some Project", }, Regions: []clientconfig.Region{ { Name: "PHL1", Values: clientconfig.Cloud{AuthInfo: &clientconfig.AuthInfo{AuthURL: "https://phl1.example.com:5000/v3"}}, }, { Name: "PHL2", Values: clientconfig.Cloud{}, }, }, Verify: &iTrue, } var disconnected_regions = []clientconfig.Region{ { Name: "SOMEWHERE", Values: clientconfig.Cloud{AuthInfo: &clientconfig.AuthInfo{AuthURL: "https://somewhere.example.com:5000/v3"}}, }, { Name: "ANYWHERE", Values: clientconfig.Cloud{AuthInfo: &clientconfig.AuthInfo{AuthURL: "https://anywhere.example.com:5000/v3"}}, }, { Name: "NOWHERE", Values: clientconfig.Cloud{AuthInfo: &clientconfig.AuthInfo{AuthURL: "https://nowhere.example.com:5000/v3"}}, }, } var DisconnectedSomewhereCloudYAML = clientconfig.Cloud{ AuthInfo: &clientconfig.AuthInfo{ AuthURL: "https://somewhere.example.com:5000/v3", Username: "jdoe", Password: "password", ProjectName: "Some Project", }, Regions: disconnected_regions, Verify: &iTrue, } var DisconnectedAnywhereCloudYAML = clientconfig.Cloud{ AuthInfo: &clientconfig.AuthInfo{ AuthURL: "https://anywhere.example.com:5000/v3", Username: "jdoe", Password: "password", ProjectName: "Some Project", }, Regions: disconnected_regions, Verify: &iTrue, } var DisconnectedNowhereCloudYAML = clientconfig.Cloud{ AuthInfo: &clientconfig.AuthInfo{ AuthURL: "https://nowhere.example.com:5000/v3", Username: "jdoe", Password: "password", ProjectName: "Some Project", }, Regions: disconnected_regions, Verify: &iTrue, } var ChicagoCloudYAML = clientconfig.Cloud{ Profile: "rackspace", RegionName: "ORD", AuthInfo: &clientconfig.AuthInfo{ AuthURL: "https://identity.api.rackspacecloud.com/v2.0/", Username: "jdoe", Password: "password", ProjectName: "Some Project", }, Verify: &iTrue, } var ChicagoCloudLegacyYAML = clientconfig.Cloud{ Cloud: "rackspace", RegionName: "ORD", AuthInfo: &clientconfig.AuthInfo{ AuthURL: "https://identity.api.rackspacecloud.com/v2.0/", Username: "jdoe", Password: "password", ProjectName: "Some Project", }, Verify: &iTrue, } var ChicagoCloudUseProfileYAML = clientconfig.Cloud{ Cloud: "rackspace", Profile: "rackspace", RegionName: "ORD", AuthInfo: &clientconfig.AuthInfo{ AuthURL: "https://identity.api.rackspacecloud.com/v2.0/", Username: "jdoe", Password: "securepassword", ProjectName: "Some Project", }, Verify: &iTrue, } var HawaiiCloudYAML = clientconfig.Cloud{ RegionName: "HNL", AuthInfo: &clientconfig.AuthInfo{ AuthURL: "https://hi.example.com:5000/v3", Username: "jdoe", Password: "password", ProjectName: "Some Project", DomainName: "default", }, Verify: &iTrue, } var HawaiiClientOpts = &clientconfig.ClientOpts{ AuthInfo: &clientconfig.AuthInfo{ AuthURL: "https://hi.example.com:5000/v3", Username: "jdoe", Password: "password", ProjectName: "Some Project", DomainName: "default", }, } var HawaiiEnvAuth = map[string]string{ "OS_AUTH_URL": "https://hi.example.com:5000/v3", "OS_USERNAME": "jdoe", "OS_PASSWORD": "password", "OS_PROJECT_NAME": "Some Project", "OS_DOMAIN_NAME": "default", } var HawaiiAuthOpts = &gophercloud.AuthOptions{ Scope: &gophercloud.AuthScope{ ProjectName: "Some Project", DomainName: "default", }, IdentityEndpoint: "https://hi.example.com:5000/v3", Username: "jdoe", Password: "password", TenantName: "Some Project", DomainName: "default", } var FloridaCloudYAML = clientconfig.Cloud{ RegionName: "MIA", Interface: "admin", AuthInfo: &clientconfig.AuthInfo{ AuthURL: "https://fl.example.com:5000/v3", Username: "jdoe", Password: "password", ProjectID: "12345", UserDomainID: "abcde", }, Verify: &iTrue, } var InsecureFloridaCloudYAML = clientconfig.Cloud{ RegionName: "MIA", AuthInfo: &clientconfig.AuthInfo{ AuthURL: "https://fl.example.com:5000/v3", Username: "jdoe", Password: "password", ProjectID: "12345", UserDomainID: "abcde", }, Verify: &iFalse, ClientKeyFile: "", ClientCertFile: "", CACertFile: "", } var SecureFloridaCloudYAML = clientconfig.Cloud{ RegionName: "MIA", AuthInfo: &clientconfig.AuthInfo{ AuthURL: "https://fl.example.com:5000/v3", Username: "jdoe", Password: "password", ProjectID: "12345", UserDomainID: "abcde", }, Verify: &iTrue, ClientKeyFile: "/home/myhome/client-cert.key", ClientCertFile: "/home/myhome/client-cert.crt", CACertFile: "/home/myhome/ca.crt", } var FloridaClientOpts = &clientconfig.ClientOpts{ AuthInfo: &clientconfig.AuthInfo{ AuthURL: "https://fl.example.com:5000/v3", Username: "jdoe", Password: "password", ProjectID: "12345", UserDomainID: "abcde", }, } var FloridaEnvAuth = map[string]string{ "OS_AUTH_URL": "https://fl.example.com:5000/v3", "OS_USERNAME": "jdoe", "OS_PASSWORD": "password", "OS_PROJECT_ID": "12345", "OS_USER_DOMAIN_ID": "abcde", } var FloridaAuthOpts = &gophercloud.AuthOptions{ Scope: &gophercloud.AuthScope{ ProjectID: "12345", }, IdentityEndpoint: "https://fl.example.com:5000/v3", Username: "jdoe", Password: "password", TenantID: "12345", DomainID: "abcde", } var CaliforniaCloudYAML = clientconfig.Cloud{ EndpointType: "internal", Regions: []clientconfig.Region{{ Name: "SAN", }, { Name: "LAX", }}, AuthInfo: &clientconfig.AuthInfo{ AuthURL: "https://ca.example.com:5000/v3", Username: "jdoe", Password: "password", ProjectName: "Some Project", ProjectDomainName: "default", UserDomainName: "default", }, Verify: &iTrue, } var CaliforniaClientOpts = &clientconfig.ClientOpts{ AuthInfo: &clientconfig.AuthInfo{ AuthURL: "https://ca.example.com:5000/v3", Username: "jdoe", Password: "password", ProjectName: "Some Project", ProjectDomainName: "default", UserDomainName: "default", }, } var CaliforniaEnvAuth = map[string]string{ "OS_AUTH_URL": "https://ca.example.com:5000/v3", "OS_USERNAME": "jdoe", "OS_PASSWORD": "password", "OS_PROJECT_NAME": "Some Project", "OS_PROJECT_DOMAIN_NAME": "default", "OS_USER_DOMAIN_NAME": "default", } var CaliforniaAuthOpts = &gophercloud.AuthOptions{ Scope: &gophercloud.AuthScope{ ProjectName: "Some Project", DomainName: "default", }, IdentityEndpoint: "https://ca.example.com:5000/v3", Username: "jdoe", Password: "password", TenantName: "Some Project", DomainName: "default", } var ArizonaCloudYAML = clientconfig.Cloud{ RegionName: "PHX", EndpointType: "public", AuthType: clientconfig.AuthToken, AuthInfo: &clientconfig.AuthInfo{ AuthURL: "https://az.example.com:5000/v3", Token: "12345", ProjectName: "Some Project", DomainName: "default", }, Verify: &iTrue, } var ArizonaClientOpts = &clientconfig.ClientOpts{ AuthInfo: &clientconfig.AuthInfo{ AuthURL: "https://az.example.com:5000/v3", Token: "12345", ProjectName: "Some Project", DomainName: "default", }, } var ArizonaEnvAuth = map[string]string{ "OS_AUTH_URL": "https://az.example.com:5000/v3", "OS_TOKEN": "12345", "OS_PROJECT_NAME": "Some Project", "OS_DOMAIN_NAME": "default", } var ArizonaAuthOpts = &gophercloud.AuthOptions{ Scope: &gophercloud.AuthScope{ ProjectName: "Some Project", DomainName: "default", }, IdentityEndpoint: "https://az.example.com:5000/v3", TokenID: "12345", TenantName: "Some Project", } var NewMexicoCloudYAML = clientconfig.Cloud{ RegionName: "SAF", EndpointType: "admin", AuthType: clientconfig.AuthPassword, AuthInfo: &clientconfig.AuthInfo{ AuthURL: "https://nm.example.com:5000/v3", Username: "jdoe", Password: "password", ProjectName: "Some Project", ProjectDomainName: "Some Domain", UserDomainName: "Some OtherDomain", DomainName: "default", }, Verify: &iTrue, } var NewMexicoClientOpts = &clientconfig.ClientOpts{ AuthInfo: &clientconfig.AuthInfo{ AuthURL: "https://nm.example.com:5000/v3", Username: "jdoe", Password: "password", ProjectName: "Some Project", ProjectDomainName: "Some Domain", UserDomainName: "Other Domain", DomainName: "default", }, } var NewMexicoEnvAuth = map[string]string{ "OS_AUTH_URL": "https://nm.example.com:5000/v3", "OS_USERNAME": "jdoe", "OS_PASSWORD": "password", "OS_PROJECT_NAME": "Some Project", "OS_PROJECT_DOMAIN_NAME": "Some Domain", "OS_USER_DOMAIN_NAME": "Other Domain", "OS_DOMAIN_NAME": "default", } var NewMexicoAuthOpts = &gophercloud.AuthOptions{ Scope: &gophercloud.AuthScope{ ProjectName: "Some Project", DomainName: "Some Domain", }, IdentityEndpoint: "https://nm.example.com:5000/v3", Username: "jdoe", Password: "password", TenantName: "Some Project", DomainName: "Other Domain", } var NevadaCloudYAML = clientconfig.Cloud{ RegionName: "LAS", AuthType: "password", EndpointType: "internal", AuthInfo: &clientconfig.AuthInfo{ AuthURL: "https://nv.example.com:5000/v3", UserID: "12345", Password: "password", ProjectName: "Some Project", ProjectDomainName: "Some Domain", }, Verify: &iTrue, } var NevadaClientOpts = &clientconfig.ClientOpts{ AuthInfo: &clientconfig.AuthInfo{ AuthURL: "https://nv.example.com:5000/v3", UserID: "12345", Password: "password", ProjectName: "Some Project", ProjectDomainName: "Some Domain", }, } var NevadaEnvAuth = map[string]string{ "OS_AUTH_URL": "https://nv.example.com:5000/v3", "OS_USER_ID": "12345", "OS_PASSWORD": "password", "OS_PROJECT_NAME": "Some Project", "OS_PROJECT_DOMAIN_NAME": "Some Domain", } var NevadaAuthOpts = &gophercloud.AuthOptions{ Scope: &gophercloud.AuthScope{ ProjectName: "Some Project", DomainName: "Some Domain", }, IdentityEndpoint: "https://nv.example.com:5000/v3", UserID: "12345", Password: "password", TenantName: "Some Project", } var TexasCloudYAML = clientconfig.Cloud{ RegionName: "AUS", AuthInfo: &clientconfig.AuthInfo{ AuthURL: "https://tx.example.com:5000/v3", Username: "jdoe", Password: "password", ProjectName: "Some Project", UserDomainName: "Some Domain", DefaultDomain: "default", }, Verify: &iTrue, } var TexasClientOpts = &clientconfig.ClientOpts{ AuthInfo: &clientconfig.AuthInfo{ AuthURL: "https://tx.example.com:5000/v3", Username: "jdoe", Password: "password", ProjectName: "Some Project", UserDomainName: "Some Domain", DefaultDomain: "default", }, } var TexasEnvAuth = map[string]string{ "OS_AUTH_URL": "https://tx.example.com:5000/v3", "OS_USERNAME": "jdoe", "OS_PASSWORD": "password", "OS_PROJECT_NAME": "Some Project", "OS_USER_DOMAIN_NAME": "Some Domain", "OS_DEFAULT_DOMAIN": "default", } var TexasAuthOpts = &gophercloud.AuthOptions{ Scope: &gophercloud.AuthScope{ ProjectName: "Some Project", DomainID: "default", }, IdentityEndpoint: "https://tx.example.com:5000/v3", Username: "jdoe", Password: "password", TenantName: "Some Project", DomainName: "Some Domain", } var CloudYAML = clientconfig.Clouds{ Clouds: map[string]clientconfig.Cloud{ "hawaii": HawaiiCloudYAML, "florida": FloridaCloudYAML, "california": CaliforniaCloudYAML, "arizona": ArizonaCloudYAML, "newmexico": NewMexicoCloudYAML, "nevada": NevadaCloudYAML, "texas": TexasCloudYAML, }, } var AlbertaCloudYAML = clientconfig.Cloud{ RegionName: "YYC", AuthType: clientconfig.AuthPassword, AuthInfo: &clientconfig.AuthInfo{ AuthURL: "https://ab.example.com:5000/v2.0", Username: "jdoe", Password: "password", ProjectName: "Some Project", }, Verify: &iTrue, } var AlbertaClientOpts = &clientconfig.ClientOpts{ AuthInfo: &clientconfig.AuthInfo{ AuthURL: "https://ab.example.com:5000/v2.0", Username: "jdoe", Password: "password", ProjectName: "Some Project", }, } var AlbertaEnvAuth = map[string]string{ "OS_AUTH_URL": "https://ab.example.com:5000/v2.0", "OS_USERNAME": "jdoe", "OS_PASSWORD": "password", "OS_PROJECT_NAME": "Some Project", "OS_IDENTITY_API_VERSION": "2.0", } var AlbertaAuthOpts = &gophercloud.AuthOptions{ IdentityEndpoint: "https://ab.example.com:5000/v2.0", Username: "jdoe", Password: "password", TenantName: "Some Project", } var YukonCloudYAML = clientconfig.Cloud{ RegionName: "YXY", AuthType: clientconfig.AuthV2Token, AuthInfo: &clientconfig.AuthInfo{ AuthURL: "https://yt.example.com:5000/v2.0", Token: "12345", ProjectName: "Some Project", }, Verify: &iTrue, } var YukonClientOpts = &clientconfig.ClientOpts{ AuthInfo: &clientconfig.AuthInfo{ AuthURL: "https://yt.example.com:5000/v2.0", Token: "12345", ProjectName: "Some Project", }, } var YukonEnvAuth = map[string]string{ "OS_AUTH_URL": "https://yt.example.com:5000/v2.0", "OS_TOKEN": "12345", "OS_PROJECT_NAME": "Some Project", "OS_IDENTITY_API_VERSION": "2", } var YukonAuthOpts = &gophercloud.AuthOptions{ IdentityEndpoint: "https://yt.example.com:5000/v2.0", TokenID: "12345", TenantName: "Some Project", } var LegacyCloudYAML = clientconfig.Clouds{ Clouds: map[string]clientconfig.Cloud{ "alberta": AlbertaCloudYAML, "yukon": YukonCloudYAML, }, } requests_test.go000066400000000000000000000240361467426574700336060ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/clientconfig/testingpackage testing import ( "fmt" "log" "os" "testing" "github.com/gophercloud/gophercloud" "github.com/gophercloud/utils/openstack/clientconfig" th "github.com/gophercloud/gophercloud/testhelper" yaml "gopkg.in/yaml.v2" ) func TestGetCloudFromYAML(t *testing.T) { allClientOpts := map[string]*clientconfig.ClientOpts{ "hawaii": { Cloud: "hawaii", EnvPrefix: "FOO", }, "california": { Cloud: "california", EnvPrefix: "FOO", }, "florida_insecure": {Cloud: "florida_insecure"}, "florida_secure": {Cloud: "florida_secure"}, "nevada": {Cloud: "nevada"}, "texas": {Cloud: "texas"}, "alberta": {Cloud: "alberta"}, "yukon": {Cloud: "yukon"}, "chicago": {Cloud: "chicago"}, "chicago_legacy": {Cloud: "chicago_legacy"}, "chicago_useprofile": {Cloud: "chicago_useprofile"}, "philadelphia": {Cloud: "philadelphia"}, "philadelphia_phl1": { Cloud: "philadelphia_complex", RegionName: "PHL1", }, "philadelphia_phl2": { Cloud: "philadelphia_complex", RegionName: "PHL2", }, "virginia": {Cloud: "virginia"}, "disconnected_smw": { Cloud: "disconnected_clouds", RegionName: "SOMEWHERE", }, "disconnected_anw": { Cloud: "disconnected_clouds", RegionName: "ANYWHERE", }, "disconnected_now": { Cloud: "disconnected_clouds", RegionName: "NOWHERE", }, } expectedClouds := map[string]*clientconfig.Cloud{ "hawaii": &HawaiiCloudYAML, "california": &CaliforniaCloudYAML, "florida_insecure": &InsecureFloridaCloudYAML, "florida_secure": &SecureFloridaCloudYAML, "nevada": &NevadaCloudYAML, "texas": &TexasCloudYAML, "alberta": &AlbertaCloudYAML, "yukon": &YukonCloudYAML, "chicago": &ChicagoCloudYAML, "chicago_legacy": &ChicagoCloudLegacyYAML, "chicago_useprofile": &ChicagoCloudUseProfileYAML, "philadelphia": &PhiladelphiaCloudYAML, "philadelphia_phl1": &PhiladelphiaComplexPhl1CloudYAML, "philadelphia_phl2": &PhiladelphiaComplexPhl2CloudYAML, "virginia": &VirginiaCloudYAML, "disconnected_smw": &DisconnectedSomewhereCloudYAML, "disconnected_anw": &DisconnectedAnywhereCloudYAML, "disconnected_now": &DisconnectedNowhereCloudYAML, } for cloud, clientOpts := range allClientOpts { actual, err := clientconfig.GetCloudFromYAML(clientOpts) th.AssertNoErr(t, err) th.AssertDeepEquals(t, expectedClouds[cloud], actual) } } func TestGetCloudFromYAMLOSCLOUD(t *testing.T) { os.Setenv("OS_CLOUD", "california") defer os.Unsetenv("OS_CLOUD") clientOpts := &clientconfig.ClientOpts{ Cloud: "hawaii", } actual, err := clientconfig.GetCloudFromYAML(clientOpts) th.AssertNoErr(t, err) th.AssertDeepEquals(t, &HawaiiCloudYAML, actual) } func TestGetCloudFromYAMLMissingClientOpts(t *testing.T) { os.Setenv("OS_CLOUD", "california") defer os.Unsetenv("OS_CLOUD") actual, err := clientconfig.GetCloudFromYAML(nil) th.AssertNoErr(t, err) th.AssertDeepEquals(t, &CaliforniaCloudYAML, actual) } func TestAuthOptionsExplicitCloud(t *testing.T) { os.Unsetenv("OS_CLOUD") clientOpts := &clientconfig.ClientOpts{ Cloud: "hawaii", } actual, err := clientconfig.AuthOptions(clientOpts) if err != nil { t.Fatal(err) } th.AssertDeepEquals(t, HawaiiAuthOpts, actual) } func TestAuthOptionsOSCLOUD(t *testing.T) { os.Setenv("FOO_CLOUD", "hawaii") defer os.Unsetenv("FOO_CLOUD") clientOpts := &clientconfig.ClientOpts{ EnvPrefix: "FOO_", } actual, err := clientconfig.AuthOptions(clientOpts) if err != nil { t.Fatal(err) } th.AssertDeepEquals(t, HawaiiAuthOpts, actual) } func TestAuthOptionsExplicitCloudAndOSCLOUD(t *testing.T) { os.Setenv("FOO_CLOUD", "hawaii") defer os.Unsetenv("FOO_CLOUD") clientOpts := &clientconfig.ClientOpts{ EnvPrefix: "FOO_", Cloud: "california", } actual, err := clientconfig.AuthOptions(clientOpts) if err != nil { t.Fatal(err) } // We should have ignored the cloud configuration option th.AssertDeepEquals(t, CaliforniaAuthOpts, actual) } func TestAuthOptionsMissingClientOpts(t *testing.T) { os.Setenv("OS_CLOUD", "hawaii") defer os.Unsetenv("OS_CLOUD") actual, err := clientconfig.AuthOptions(nil) if err != nil { t.Fatal(err) } // We should have handled the missing config opts and fallen back to // defaults th.AssertDeepEquals(t, HawaiiAuthOpts, actual) } func TestAuthOptionsCreationFromCloudsYAML(t *testing.T) { os.Unsetenv("OS_CLOUD") allClouds := map[string]*gophercloud.AuthOptions{ "hawaii": HawaiiAuthOpts, "florida": FloridaAuthOpts, "california": CaliforniaAuthOpts, "arizona": ArizonaAuthOpts, "newmexico": NewMexicoAuthOpts, "nevada": NevadaAuthOpts, "texas": TexasAuthOpts, } for cloud, expected := range allClouds { clientOpts := &clientconfig.ClientOpts{ Cloud: cloud, } actual, err := clientconfig.AuthOptions(clientOpts) th.AssertNoErr(t, err) th.AssertDeepEquals(t, expected, actual) scope, err := expected.ToTokenV3ScopeMap() th.AssertNoErr(t, err) _, err = expected.ToTokenV3CreateMap(scope) th.AssertNoErr(t, err) } } func TestAuthOptionsCreationFromLegacyCloudsYAML(t *testing.T) { os.Unsetenv("OS_CLOUD") allClouds := map[string]*gophercloud.AuthOptions{ "alberta": AlbertaAuthOpts, "yukon": YukonAuthOpts, } for cloud, expected := range allClouds { clientOpts := &clientconfig.ClientOpts{ Cloud: cloud, } actual, err := clientconfig.AuthOptions(clientOpts) th.AssertNoErr(t, err) th.AssertDeepEquals(t, expected, actual) _, err = expected.ToTokenV2CreateMap() th.AssertNoErr(t, err) } } func TestAuthOptionsCreationFromClientConfig(t *testing.T) { os.Unsetenv("OS_CLOUD") expectedAuthOpts := map[string]*gophercloud.AuthOptions{ "hawaii": HawaiiAuthOpts, "florida": FloridaAuthOpts, "california": CaliforniaAuthOpts, "arizona": ArizonaAuthOpts, "newmexico": NewMexicoAuthOpts, "nevada": NevadaAuthOpts, "texas": TexasAuthOpts, } allClientOpts := map[string]*clientconfig.ClientOpts{ "hawaii": HawaiiClientOpts, "florida": FloridaClientOpts, "california": CaliforniaClientOpts, "arizona": ArizonaClientOpts, "newmexico": NewMexicoClientOpts, "nevada": NevadaClientOpts, "texas": TexasClientOpts, } for cloud, clientOpts := range allClientOpts { actualAuthOpts, err := clientconfig.AuthOptions(clientOpts) th.AssertNoErr(t, err) th.AssertDeepEquals(t, expectedAuthOpts[cloud], actualAuthOpts) } } func TestAuthOptionsCreationFromLegacyClientConfig(t *testing.T) { os.Unsetenv("OS_CLOUD") expectedAuthOpts := map[string]*gophercloud.AuthOptions{ "alberta": AlbertaAuthOpts, "yukon": YukonAuthOpts, } allClientOpts := map[string]*clientconfig.ClientOpts{ "alberta": AlbertaClientOpts, "yukon": YukonClientOpts, } for cloud, clientOpts := range allClientOpts { actualAuthOpts, err := clientconfig.AuthOptions(clientOpts) th.AssertNoErr(t, err) th.AssertDeepEquals(t, expectedAuthOpts[cloud], actualAuthOpts) } } func TestAuthOptionsCreationFromEnv(t *testing.T) { os.Unsetenv("OS_CLOUD") allEnvVars := map[string]map[string]string{ "hawaii": HawaiiEnvAuth, "florida": FloridaEnvAuth, "california": CaliforniaEnvAuth, "arizona": ArizonaEnvAuth, "newmexico": NewMexicoEnvAuth, "nevada": NevadaEnvAuth, "texas": TexasEnvAuth, "virginia": VirginiaEnvAuth, } expectedAuthOpts := map[string]*gophercloud.AuthOptions{ "hawaii": HawaiiAuthOpts, "florida": FloridaAuthOpts, "california": CaliforniaAuthOpts, "arizona": ArizonaAuthOpts, "newmexico": NewMexicoAuthOpts, "nevada": NevadaAuthOpts, "texas": TexasAuthOpts, "virginia": VirginiaAuthOpts, } for cloud, envVars := range allEnvVars { t.Run(cloud, func(t *testing.T) { for k, v := range envVars { os.Setenv(k, v) defer os.Unsetenv(k) } actualAuthOpts, err := clientconfig.AuthOptions(nil) th.AssertNoErr(t, err) th.AssertDeepEquals(t, expectedAuthOpts[cloud], actualAuthOpts) }) } } func TestAuthOptionsCreationFromLegacyEnv(t *testing.T) { os.Unsetenv("OS_CLOUD") allEnvVars := map[string]map[string]string{ "alberta": AlbertaEnvAuth, "yukon": YukonEnvAuth, } expectedAuthOpts := map[string]*gophercloud.AuthOptions{ "alberta": AlbertaAuthOpts, "yukon": YukonAuthOpts, } for cloud, envVars := range allEnvVars { t.Run(cloud, func(t *testing.T) { for k, v := range envVars { os.Setenv(k, v) defer os.Unsetenv(k) } actualAuthOpts, err := clientconfig.AuthOptions(nil) th.AssertNoErr(t, err) th.AssertDeepEquals(t, expectedAuthOpts[cloud], actualAuthOpts) }) } } type CustomYAMLOpts struct { Logger *log.Logger } func (opts CustomYAMLOpts) LoadCloudsYAML() (map[string]clientconfig.Cloud, error) { filename, content, err := clientconfig.FindAndReadCloudsYAML() if err != nil { return nil, err } var clouds clientconfig.Clouds err = yaml.Unmarshal(content, &clouds) if err != nil { return nil, fmt.Errorf("failed to unmarshal yaml: %v", err) } opts.Logger.Printf("Filename: %s", filename) return clouds.Clouds, nil } func (opts CustomYAMLOpts) LoadSecureCloudsYAML() (map[string]clientconfig.Cloud, error) { return nil, nil } func (opts CustomYAMLOpts) LoadPublicCloudsYAML() (map[string]clientconfig.Cloud, error) { return nil, nil } func TestGetCloudFromYAMLWithCustomYAMLOpts(t *testing.T) { logger := log.New(os.Stderr, "", log.Lshortfile) yamlOpts := CustomYAMLOpts{ Logger: logger, } allClientOpts := map[string]*clientconfig.ClientOpts{ "hawaii": { Cloud: "hawaii", EnvPrefix: "FOO", YAMLOpts: yamlOpts, }, "california": { Cloud: "california", EnvPrefix: "FOO", YAMLOpts: yamlOpts, }, } expectedClouds := map[string]*clientconfig.Cloud{ "hawaii": &HawaiiCloudYAML, "california": &CaliforniaCloudYAML, } for cloud, clientOpts := range allClientOpts { actual, err := clientconfig.GetCloudFromYAML(clientOpts) th.AssertNoErr(t, err) th.AssertDeepEquals(t, expectedClouds[cloud], actual) } } results_test.go000066400000000000000000000022771467426574700334370ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/clientconfig/testingpackage testing import ( "testing" "github.com/gophercloud/utils/openstack/clientconfig" th "github.com/gophercloud/gophercloud/testhelper" yaml "gopkg.in/yaml.v2" ) var VirginiaExpected = `clouds: virginia: auth: auth_url: https://va.example.com:5000/v3 application_credential_id: app-cred-id application_credential_secret: secret auth_type: v3applicationcredential region_name: VA verify: true ` var HawaiiExpected = `clouds: hawaii: auth: auth_url: https://hi.example.com:5000/v3 username: jdoe password: password project_name: Some Project domain_name: default region_name: HNL verify: true ` func TestMarshallCloudToYaml(t *testing.T) { clouds := make(map[string]map[string]*clientconfig.Cloud) clouds["clouds"] = map[string]*clientconfig.Cloud{ "virginia": &VirginiaCloudYAML, } marshalled, err := yaml.Marshal(clouds) th.AssertNoErr(t, err) th.AssertEquals(t, VirginiaExpected, string(marshalled)) clouds["clouds"] = map[string]*clientconfig.Cloud{ "hawaii": &HawaiiCloudYAML, } marshalled, err = yaml.Marshal(clouds) th.AssertNoErr(t, err) th.AssertEquals(t, HawaiiExpected, string(marshalled)) } golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/clientconfig/testing/secure.yaml000066400000000000000000000002631467426574700325720ustar00rootroot00000000000000clouds: philadelphia: auth: username: "admin" password: "password" auth_type: "password" chicago_useprofile: auth: password: "securepassword" golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/clientconfig/utils.go000066400000000000000000000122601467426574700304320ustar00rootroot00000000000000package clientconfig import ( "encoding/json" "fmt" "io/ioutil" "os" "os/user" "path/filepath" "reflect" "github.com/gophercloud/gophercloud" "github.com/gophercloud/utils/env" ) // defaultIfEmpty is a helper function to make it cleaner to set default value // for strings. func defaultIfEmpty(value string, defaultValue string) string { if value == "" { return defaultValue } return value } // mergeCLouds merges two Clouds recursively (the AuthInfo also gets merged). // In case both Clouds define a value, the value in the 'override' cloud takes precedence func mergeClouds(override, cloud interface{}) (*Cloud, error) { overrideJson, err := json.Marshal(override) if err != nil { return nil, err } cloudJson, err := json.Marshal(cloud) if err != nil { return nil, err } var overrideInterface interface{} err = json.Unmarshal(overrideJson, &overrideInterface) if err != nil { return nil, err } var cloudInterface interface{} err = json.Unmarshal(cloudJson, &cloudInterface) if err != nil { return nil, err } var mergedCloud Cloud mergedInterface := mergeInterfaces(overrideInterface, cloudInterface) mergedJson, err := json.Marshal(mergedInterface) err = json.Unmarshal(mergedJson, &mergedCloud) if err != nil { return nil, err } return &mergedCloud, nil } // merges two interfaces. In cases where a value is defined for both 'overridingInterface' and // 'inferiorInterface' the value in 'overridingInterface' will take precedence. func mergeInterfaces(overridingInterface, inferiorInterface interface{}) interface{} { switch overriding := overridingInterface.(type) { case map[string]interface{}: interfaceMap, ok := inferiorInterface.(map[string]interface{}) if !ok { return overriding } for k, v := range interfaceMap { if overridingValue, ok := overriding[k]; ok { overriding[k] = mergeInterfaces(overridingValue, v) } else { overriding[k] = v } } case []interface{}: list, ok := inferiorInterface.([]interface{}) if !ok { return overriding } for i := range list { overriding = append(overriding, list[i]) } return overriding case nil: // mergeClouds(nil, map[string]interface{...}) -> map[string]interface{...} v, ok := inferiorInterface.(map[string]interface{}) if ok { return v } } // We don't want to override with empty values if reflect.DeepEqual(overridingInterface, nil) || reflect.DeepEqual(reflect.Zero(reflect.TypeOf(overridingInterface)).Interface(), overridingInterface) { return inferiorInterface } else { return overridingInterface } } // FindAndReadCloudsYAML attempts to locate a clouds.yaml file in the following // locations: // // 1. OS_CLIENT_CONFIG_FILE // 2. Current directory. // 3. unix-specific user_config_dir (~/.config/openstack/clouds.yaml) // 4. unix-specific site_config_dir (/etc/openstack/clouds.yaml) // // If found, the contents of the file is returned. func FindAndReadCloudsYAML() (string, []byte, error) { // OS_CLIENT_CONFIG_FILE if v := env.Getenv("OS_CLIENT_CONFIG_FILE"); v != "" { if ok := fileExists(v); ok { content, err := ioutil.ReadFile(v) return v, content, err } } s, b, err := FindAndReadYAML("clouds.yaml") if s == "" { return FindAndReadYAML("clouds.yml") } return s, b, err } func FindAndReadPublicCloudsYAML() (string, []byte, error) { s, b, err := FindAndReadYAML("clouds-public.yaml") if s == "" { return FindAndReadYAML("clouds-public.yml") } return s, b, err } func FindAndReadSecureCloudsYAML() (string, []byte, error) { s, b, err := FindAndReadYAML("secure.yaml") if s == "" { return FindAndReadYAML("secure.yml") } return s, b, err } func FindAndReadYAML(yamlFile string) (string, []byte, error) { // current directory cwd, err := os.Getwd() if err != nil { return "", nil, fmt.Errorf("unable to determine working directory: %w", err) } filename := filepath.Join(cwd, yamlFile) if ok := fileExists(filename); ok { content, err := ioutil.ReadFile(filename) return filename, content, err } // unix user config directory: ~/.config/openstack. if currentUser, err := user.Current(); err == nil { homeDir := currentUser.HomeDir if homeDir != "" { filename := filepath.Join(homeDir, ".config/openstack/"+yamlFile) if ok := fileExists(filename); ok { content, err := ioutil.ReadFile(filename) return filename, content, err } } } // unix-specific site config directory: /etc/openstack. filename = "/etc/openstack/" + yamlFile if ok := fileExists(filename); ok { content, err := ioutil.ReadFile(filename) return filename, content, err } return "", nil, fmt.Errorf("no %s file found: %w", yamlFile, os.ErrNotExist) } // fileExists checks for the existence of a file at a given location. func fileExists(filename string) bool { if _, err := os.Stat(filename); err == nil { return true } return false } // GetEndpointType is a helper method to determine the endpoint type // requested by the user. func GetEndpointType(endpointType string) gophercloud.Availability { if endpointType == "internal" || endpointType == "internalURL" { return gophercloud.AvailabilityInternal } if endpointType == "admin" || endpointType == "adminURL" { return gophercloud.AvailabilityAdmin } return gophercloud.AvailabilityPublic } golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/compute/000077500000000000000000000000001467426574700257525ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/compute/v2/000077500000000000000000000000001467426574700263015ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/compute/v2/availabilityzones/000077500000000000000000000000001467426574700320325ustar00rootroot00000000000000utils.go000066400000000000000000000013671467426574700334510ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/compute/v2/availabilityzonespackage availabilityzones import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones" ) // ListAvailableAvailabilityZones is a convenience function that return a slice of available Availability Zones. func ListAvailableAvailabilityZones(client *gophercloud.ServiceClient) ([]string, error) { var ret []string allPages, err := availabilityzones.List(client).AllPages() if err != nil { return ret, err } availabilityZoneInfo, err := availabilityzones.ExtractAvailabilityZones(allPages) if err != nil { return ret, err } for _, zoneInfo := range availabilityZoneInfo { if zoneInfo.ZoneState.Available { ret = append(ret, zoneInfo.ZoneName) } } return ret, nil } golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/compute/v2/flavors/000077500000000000000000000000001467426574700277555ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/compute/v2/flavors/utils.go000066400000000000000000000023131467426574700314430ustar00rootroot00000000000000package flavors import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors" ) // IDFromName is a convenience function that returns a flavor's ID given its // name. Errors when the number of items found is not one. func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { IDs, err := IDsFromName(client, name) if err != nil { return "", err } switch count := len(IDs); count { case 0: return "", &gophercloud.ErrResourceNotFound{Name: name, ResourceType: "flavor"} case 1: return IDs[0], nil default: return "", &gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "flavor"} } } // IDsFromName returns zero or more IDs corresponding to a name. The returned // error is only non-nil in case of failure. func IDsFromName(client *gophercloud.ServiceClient, name string) ([]string, error) { pages, err := flavors.ListDetail(client, nil).AllPages() if err != nil { return nil, err } all, err := flavors.ExtractFlavors(pages) if err != nil { return nil, err } IDs := make([]string, 0, len(all)) for _, s := range all { if s.Name == name { IDs = append(IDs, s.ID) } } return IDs, nil } golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/compute/v2/servers/000077500000000000000000000000001467426574700277725ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/compute/v2/servers/utils.go000066400000000000000000000024061467426574700314630ustar00rootroot00000000000000package servers import ( "fmt" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" ) // IDFromName is a convenience function that returns a server's ID given its // name. Errors when the number of items found is not one. func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { IDs, err := IDsFromName(client, name) if err != nil { return "", err } switch count := len(IDs); count { case 0: return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "server"} case 1: return IDs[0], nil default: return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "server"} } } // IDsFromName returns zero or more IDs corresponding to a name. The returned // error is only non-nil in case of failure. func IDsFromName(client *gophercloud.ServiceClient, name string) ([]string, error) { pages, err := servers.List(client, servers.ListOpts{ // nova list uses a name field as a regexp Name: fmt.Sprintf("^%s$", name), }).AllPages() if err != nil { return nil, err } all, err := servers.ExtractServers(pages) if err != nil { return nil, err } IDs := make([]string, len(all)) for i := range all { IDs[i] = all[i].ID } return IDs, nil } golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/helpers/000077500000000000000000000000001467426574700257405ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/helpers/doc.go000066400000000000000000000024651467426574700270430ustar00rootroot00000000000000/* Package helpers collects generic functionality over the Gophercloud OpenStack Go SDK. # OpenStack Project Purge Set of method to purge resources associated to an OpenStack project. This is partially inspired on the following projects: - https://docs.openstack.org/python-openstackclient/latest/cli/command-objects/project-purge.html - https://docs.openstack.org/neutron/latest/admin/ops-resource-purge.html - https://opendev.org/x/ospurge Example to Purge all the resources and Delete a Project purgeOpts := ProjectPurgeOpts { StoragePurgeOpts: &StoragePurgeOpts{storageClient}, ComputePurgeOpts: &ComputePurgeOpts{computeClient}, NetworkPurgeOpts: &NetworkPurgeOpts{networkClient}, } projectID := "966b3c7d36a24facaf20b7e458bf2192" err := helpers.ProjectPurgeAll(projectID, opts) if err != nil { panic(err) } else { err = projects.Delete(identityClient, projectID).ExtractErr() if err != nil { panic(err) } } Example to Purge storage and networking resources on a Project but keep the Project itself purgeOpts := ProjectPurgeOpts { StoragePurgeOpts: &StoragePurgeOpts{storageClient}, NetworkPurgeOpts: &NetworkPurgeOpts{networkClient}, } projectID := "966b3c7d36a24facaf20b7e458bf2192" err := helpers.ProjectPurgeAll(projectID, opts) if err != nil { panic(err) } */ package helpers golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/helpers/projectpurge.go000066400000000000000000000274651467426574700310160ustar00rootroot00000000000000package helpers import ( "fmt" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/snapshots" "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes" "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/portforwarding" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups" "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" ) type ProjectPurgeOpts struct { ComputePurgeOpts *ComputePurgeOpts StoragePurgeOpts *StoragePurgeOpts NetworkPurgeOpts *NetworkPurgeOpts } type ComputePurgeOpts struct { // Client is a reference to a specific compute service client. Client *gophercloud.ServiceClient } type StoragePurgeOpts struct { // Client is a reference to a specific storage service client. Client *gophercloud.ServiceClient } type NetworkPurgeOpts struct { // Client is a reference to a specific networking service client. Client *gophercloud.ServiceClient } // ProjectPurgeAll purges all the resources associated with a project. // This includes: servers, snapshosts, volumes, floating IPs, routers, networks, sub-networks and security groups func ProjectPurgeAll(projectID string, purgeOpts ProjectPurgeOpts) (err error) { if purgeOpts.ComputePurgeOpts != nil { err = ProjectPurgeCompute(projectID, *purgeOpts.ComputePurgeOpts) if err != nil { return err } } if purgeOpts.StoragePurgeOpts != nil { err = ProjectPurgeStorage(projectID, *purgeOpts.StoragePurgeOpts) if err != nil { return err } } if purgeOpts.NetworkPurgeOpts != nil { err = ProjectPurgeNetwork(projectID, *purgeOpts.NetworkPurgeOpts) if err != nil { return err } } return nil } // ProjectPurgeCompute purges the Compute v2 resources associated with a project. // This includes: servers func ProjectPurgeCompute(projectID string, purgeOpts ComputePurgeOpts) (err error) { // Delete servers listOpts := servers.ListOpts{ AllTenants: true, TenantID: projectID, } allPages, err := servers.List(purgeOpts.Client, listOpts).AllPages() if err != nil { return fmt.Errorf("Error finding servers for project: " + projectID) } allServers, err := servers.ExtractServers(allPages) if err != nil { return fmt.Errorf("Error extracting servers for project: " + projectID) } if len(allServers) > 0 { for _, server := range allServers { err = servers.Delete(purgeOpts.Client, server.ID).ExtractErr() if err != nil { return fmt.Errorf("Error deleting server: " + server.Name + " from project: " + projectID) } } } return nil } // ProjectPurgeStorage purges the Blockstorage v3 resources associated with a project. // This includes: snapshosts and volumes func ProjectPurgeStorage(projectID string, purgeOpts StoragePurgeOpts) (err error) { // Delete snapshots err = clearBlockStorageSnaphosts(projectID, purgeOpts.Client) if err != nil { return err } // Delete volumes err = clearBlockStorageVolumes(projectID, purgeOpts.Client) if err != nil { return err } return nil } // ProjectPurgeNetwork purges the Networking v2 resources associated with a project. // This includes: floating IPs, routers, networks, sub-networks and security groups func ProjectPurgeNetwork(projectID string, purgeOpts NetworkPurgeOpts) (err error) { // Delete floating IPs err = clearNetworkingFloatingIPs(projectID, purgeOpts.Client) if err != nil { return err } // Delete ports err = clearNetworkingPorts(projectID, purgeOpts.Client) if err != nil { return err } // Delete routers err = clearNetworkingRouters(projectID, purgeOpts.Client) if err != nil { return err } // Delete networks err = clearNetworkingNetworks(projectID, purgeOpts.Client) if err != nil { return err } // Delete security groups err = clearNetworkingSecurityGroups(projectID, purgeOpts.Client) if err != nil { return err } return nil } func clearBlockStorageVolumes(projectID string, storageClient *gophercloud.ServiceClient) error { listOpts := volumes.ListOpts{ AllTenants: true, TenantID: projectID, } allPages, err := volumes.List(storageClient, listOpts).AllPages() if err != nil { return fmt.Errorf("Error finding volumes for project: " + projectID) } allVolumes, err := volumes.ExtractVolumes(allPages) if err != nil { return fmt.Errorf("Error extracting volumes for project: " + projectID) } if len(allVolumes) > 0 { deleteOpts := volumes.DeleteOpts{ Cascade: true, } for _, volume := range allVolumes { err = volumes.Delete(storageClient, volume.ID, deleteOpts).ExtractErr() if err != nil { return fmt.Errorf("Error deleting volume: " + volume.Name + " from project: " + projectID) } } } return err } func clearBlockStorageSnaphosts(projectID string, storageClient *gophercloud.ServiceClient) error { listOpts := snapshots.ListOpts{ AllTenants: true, TenantID: projectID, } allPages, err := snapshots.List(storageClient, listOpts).AllPages() if err != nil { return fmt.Errorf("Error finding snapshots for project: " + projectID) } allSnapshots, err := snapshots.ExtractSnapshots(allPages) if err != nil { return fmt.Errorf("Error extracting snapshots for project: " + projectID) } if len(allSnapshots) > 0 { for _, snaphost := range allSnapshots { err = snapshots.Delete(storageClient, snaphost.ID).ExtractErr() if err != nil { return fmt.Errorf("Error deleting snaphost: " + snaphost.Name + " from project: " + projectID) } } } return nil } func clearPortforwarding(networkClient *gophercloud.ServiceClient, fipID string, projectID string) error { allPages, err := portforwarding.List(networkClient, portforwarding.ListOpts{}, fipID).AllPages() if err != nil { return err } allPFs, err := portforwarding.ExtractPortForwardings(allPages) if err != nil { return err } for _, pf := range allPFs { err := portforwarding.Delete(networkClient, fipID, pf.ID).ExtractErr() if err != nil { return fmt.Errorf("Error deleting floating IP port forwarding: " + pf.ID + " from project: " + projectID) } } return nil } func clearNetworkingFloatingIPs(projectID string, networkClient *gophercloud.ServiceClient) error { listOpts := floatingips.ListOpts{ TenantID: projectID, } allPages, err := floatingips.List(networkClient, listOpts).AllPages() if err != nil { return fmt.Errorf("Error finding floating IPs for project: " + projectID) } allFloatings, err := floatingips.ExtractFloatingIPs(allPages) if err != nil { return fmt.Errorf("Error extracting floating IPs for project: " + projectID) } if len(allFloatings) > 0 { for _, floating := range allFloatings { // Clear all portforwarding settings otherwise the floating IP can't be deleted err = clearPortforwarding(networkClient, floating.ID, projectID) if err != nil { return err } err = floatingips.Delete(networkClient, floating.ID).ExtractErr() if err != nil { return fmt.Errorf("Error deleting floating IP: " + floating.ID + " from project: " + projectID) } } } return nil } func clearNetworkingPorts(projectID string, networkClient *gophercloud.ServiceClient) error { listOpts := ports.ListOpts{ TenantID: projectID, } allPages, err := ports.List(networkClient, listOpts).AllPages() if err != nil { return fmt.Errorf("Error finding ports for project: " + projectID) } allPorts, err := ports.ExtractPorts(allPages) if err != nil { return fmt.Errorf("Error extracting ports for project: " + projectID) } if len(allPorts) > 0 { for _, port := range allPorts { if port.DeviceOwner == "network:ha_router_replicated_interface" { continue } err = ports.Delete(networkClient, port.ID).ExtractErr() if err != nil { return fmt.Errorf("Error deleting port: " + port.ID + " from project: " + projectID) } } } return nil } // We need all subnets to disassociate the router from the subnet func getAllSubnets(projectID string, networkClient *gophercloud.ServiceClient) ([]string, error) { subnets := make([]string, 0) listOpts := networks.ListOpts{ TenantID: projectID, } allPages, err := networks.List(networkClient, listOpts).AllPages() if err != nil { return subnets, fmt.Errorf("Error finding networks for project: " + projectID) } allNetworks, err := networks.ExtractNetworks(allPages) if err != nil { return subnets, fmt.Errorf("Error extracting networks for project: " + projectID) } if len(allNetworks) > 0 { for _, network := range allNetworks { subnets = append(subnets, network.Subnets...) } } return subnets, nil } func clearAllRouterInterfaces(projectID string, routerID string, subnets []string, networkClient *gophercloud.ServiceClient) error { for _, subnet := range subnets { intOpts := routers.RemoveInterfaceOpts{ SubnetID: subnet, } _, err := routers.RemoveInterface(networkClient, routerID, intOpts).Extract() if err != nil { return err } } return nil } func clearNetworkingRouters(projectID string, networkClient *gophercloud.ServiceClient) error { listOpts := routers.ListOpts{ TenantID: projectID, } allPages, err := routers.List(networkClient, listOpts).AllPages() if err != nil { return fmt.Errorf("Error finding routers for project: " + projectID) } allRouters, err := routers.ExtractRouters(allPages) if err != nil { return fmt.Errorf("Error extracting routers for project: " + projectID) } subnets, err := getAllSubnets(projectID, networkClient) if err != nil { return fmt.Errorf("Error fetching subnets project: " + projectID) } if len(allRouters) > 0 { for _, router := range allRouters { err = clearAllRouterInterfaces(projectID, router.ID, subnets, networkClient) if err != nil { return err } routes := []routers.Route{} // Clear all routes updateOpts := routers.UpdateOpts{ Routes: &routes, } _, err := routers.Update(networkClient, router.ID, updateOpts).Extract() if err != nil { return fmt.Errorf("Error deleting router: " + router.Name + " from project: " + projectID) } err = routers.Delete(networkClient, router.ID).ExtractErr() if err != nil { return fmt.Errorf("Error deleting router: " + router.Name + " from project: " + projectID) } } } return nil } func clearNetworkingNetworks(projectID string, networkClient *gophercloud.ServiceClient) error { listOpts := networks.ListOpts{ TenantID: projectID, } allPages, err := networks.List(networkClient, listOpts).AllPages() if err != nil { return fmt.Errorf("Error finding networks for project: " + projectID) } allNetworks, err := networks.ExtractNetworks(allPages) if err != nil { return fmt.Errorf("Error extracting networks for project: " + projectID) } if len(allNetworks) > 0 { for _, network := range allNetworks { err = networks.Delete(networkClient, network.ID).ExtractErr() if err != nil { return fmt.Errorf("Error deleting network: " + network.Name + " from project: " + projectID) } } } return nil } func clearNetworkingSecurityGroups(projectID string, networkClient *gophercloud.ServiceClient) error { listOpts := groups.ListOpts{ TenantID: projectID, } allPages, err := groups.List(networkClient, listOpts).AllPages() if err != nil { return fmt.Errorf("Error finding security groups for project: " + projectID) } allSecGroups, err := groups.ExtractGroups(allPages) if err != nil { return fmt.Errorf("Error extracting security groups for project: " + projectID) } if len(allSecGroups) > 0 { for _, group := range allSecGroups { err = groups.Delete(networkClient, group.ID).ExtractErr() if err != nil { return fmt.Errorf("Error deleting security group: " + group.Name + " from project: " + projectID) } } } return nil } golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/imageservice/000077500000000000000000000000001467426574700267415ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/imageservice/v2/000077500000000000000000000000001467426574700272705ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/imageservice/v2/images/000077500000000000000000000000001467426574700305355ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/imageservice/v2/images/utils.go000066400000000000000000000022711467426574700322260ustar00rootroot00000000000000package images import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/imageservice/v2/images" ) // IDFromName is a convenience function that returns an image's ID given its // name. Errors when the number of items found is not one. func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { IDs, err := IDsFromName(client, name) if err != nil { return "", err } switch count := len(IDs); count { case 0: return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "image"} case 1: return IDs[0], nil default: return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "image"} } } // IDsFromName returns zero or more IDs corresponding to a name. The returned // error is only non-nil in case of failure. func IDsFromName(client *gophercloud.ServiceClient, name string) ([]string, error) { pages, err := images.List(client, images.ListOpts{ Name: name, }).AllPages() if err != nil { return nil, err } all, err := images.ExtractImages(pages) if err != nil { return nil, err } IDs := make([]string, len(all)) for i := range all { IDs[i] = all[i].ID } return IDs, nil } golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/networking/000077500000000000000000000000001467426574700264655ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/networking/v2/000077500000000000000000000000001467426574700270145ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/networking/v2/extensions/000077500000000000000000000000001467426574700312135ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/networking/v2/extensions/security/000077500000000000000000000000001467426574700330625ustar00rootroot00000000000000groups/000077500000000000000000000000001467426574700343225ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/networking/v2/extensions/securityutils.go000066400000000000000000000016271467426574700360170ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/networking/v2/extensions/security/groupspackage groups import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups" ) // IDFromName is a convenience function that returns a security group's ID, // given its name. func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { count := 0 id := "" listOpts := groups.ListOpts{ Name: name, } pages, err := groups.List(client, listOpts).AllPages() if err != nil { return "", err } all, err := groups.ExtractGroups(pages) if err != nil { return "", err } for _, s := range all { if s.Name == name { count++ id = s.ID } } switch count { case 0: return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "security group"} case 1: return id, nil default: return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "security group"} } } golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/networking/v2/networks/000077500000000000000000000000001467426574700306705ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/networking/v2/networks/utils.go000066400000000000000000000023101467426574700323530ustar00rootroot00000000000000package networks import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" ) // IDFromName is a convenience function that returns a network's ID given its // name. Errors when the number of items found is not one. func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { IDs, err := IDsFromName(client, name) if err != nil { return "", err } switch count := len(IDs); count { case 0: return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "network"} case 1: return IDs[0], nil default: return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "network"} } } // IDsFromName returns zero or more IDs corresponding to a name. The returned // error is only non-nil in case of failure. func IDsFromName(client *gophercloud.ServiceClient, name string) ([]string, error) { pages, err := networks.List(client, networks.ListOpts{ Name: name, }).AllPages() if err != nil { return nil, err } all, err := networks.ExtractNetworks(pages) if err != nil { return nil, err } IDs := make([]string, len(all)) for i := range all { IDs[i] = all[i].ID } return IDs, nil } golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/networking/v2/ports/000077500000000000000000000000001467426574700301635ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/networking/v2/ports/utils.go000066400000000000000000000022551467426574700316560ustar00rootroot00000000000000package ports import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" ) // IDFromName is a convenience function that returns a port's ID given its // name. Errors when the number of items found is not one. func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { IDs, err := IDsFromName(client, name) if err != nil { return "", err } switch count := len(IDs); count { case 0: return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "port"} case 1: return IDs[0], nil default: return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "port"} } } // IDsFromName returns zero or more IDs corresponding to a name. The returned // error is only non-nil in case of failure. func IDsFromName(client *gophercloud.ServiceClient, name string) ([]string, error) { pages, err := ports.List(client, ports.ListOpts{ Name: name, }).AllPages() if err != nil { return nil, err } all, err := ports.ExtractPorts(pages) if err != nil { return nil, err } IDs := make([]string, len(all)) for i := range all { IDs[i] = all[i].ID } return IDs, nil } golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/networking/v2/subnets/000077500000000000000000000000001467426574700304775ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/networking/v2/subnets/utils.go000066400000000000000000000022771467426574700321760ustar00rootroot00000000000000package subnets import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/networking/v2/subnets" ) // IDFromName is a convenience function that returns a subnet's ID given its // name. Errors when the number of items found is not one. func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { IDs, err := IDsFromName(client, name) if err != nil { return "", err } switch count := len(IDs); count { case 0: return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "subnet"} case 1: return IDs[0], nil default: return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "subnet"} } } // IDsFromName returns zero or more IDs corresponding to a name. The returned // error is only non-nil in case of failure. func IDsFromName(client *gophercloud.ServiceClient, name string) ([]string, error) { pages, err := subnets.List(client, subnets.ListOpts{ Name: name, }).AllPages() if err != nil { return nil, err } all, err := subnets.ExtractSubnets(pages) if err != nil { return nil, err } IDs := make([]string, len(all)) for i := range all { IDs[i] = all[i].ID } return IDs, nil } golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/objectstorage/000077500000000000000000000000001467426574700271315ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/objectstorage/v1/000077500000000000000000000000001467426574700274575ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/objectstorage/v1/objects/000077500000000000000000000000001467426574700311105ustar00rootroot00000000000000download.go000066400000000000000000000246461467426574700332030ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/objectstorage/v1/objectspackage objects import ( "fmt" "io" "os" "path" "path/filepath" "strconv" "strings" "time" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers" "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects" "github.com/gophercloud/gophercloud/pagination" ) // DownloadOpts represents options used for downloading an object. type DownloadOpts struct { // Delimiter is a delimiter to specify for listing objects. Delimiter string // IgnoreMtime won't update the downloaded file's mtime. IgnoreMtime bool // NoDownload won't actually download the object. NoDownload bool // OutDirectory is a directory to save the objects to. OutDirectory string // OutFile is a file to save the object to. OutFile string // Prefix is a prefix string for a container. Prefix string // RemovePrefix will remove the prefix from the container. RemovePrefix bool // SkipIdentical will skip identical objects already downloaded. SkipIdentical bool // YesAll will download everything. YesAll bool } // Download downloads one or more objects from an Object Storage account. // It is roughly based on the python-swiftclient implementation: // // https://github.com/openstack/python-swiftclient/blob/e65070964c7b1e04119c87e5f344d39358780d18/swiftclient/service.py#L1024 func Download(client *gophercloud.ServiceClient, containerName string, objectNames []string, opts *DownloadOpts) ([]DownloadResult, error) { var downloadResults []DownloadResult if strings.Contains(containerName, "/") { return nil, fmt.Errorf("container name %s contains a /", containerName) } if containerName == "" { if opts.YesAll { // Download everything listOpts := containers.ListOpts{ Full: true, Delimiter: opts.Delimiter, Prefix: opts.Prefix, } err := containers.List(client, listOpts).EachPage(func(page pagination.Page) (bool, error) { containerList, err := containers.ExtractInfo(page) if err != nil { return false, fmt.Errorf("error listing containers: %s", err) } for _, c := range containerList { results, err := downloadContainer(client, c.Name, opts) if err != nil { return false, fmt.Errorf("error downloading container %s: %s", c.Name, err) } downloadResults = append(downloadResults, results...) } return true, nil }) if err != nil { return nil, fmt.Errorf("error downloading container %s: %s", containerName, err) } return downloadResults, nil } } if len(objectNames) == 0 { results, err := downloadContainer(client, containerName, opts) if err != nil { return nil, fmt.Errorf("error downloading container %s: %s", containerName, err) } downloadResults = append(downloadResults, results...) return downloadResults, nil } for _, objectName := range objectNames { result, err := downloadObject(client, containerName, objectName, opts) if err != nil { return nil, fmt.Errorf("error downloading object %s/%s: %s", containerName, objectName, err) } downloadResults = append(downloadResults, *result) } return downloadResults, nil } // downloadObject will download a specified object. func downloadObject(client *gophercloud.ServiceClient, containerName string, objectName string, opts *DownloadOpts) (*DownloadResult, error) { var objectDownloadOpts objects.DownloadOpts var pseudoDir bool // Perform a get on the object in order to get its metadata. originalObject := objects.Get(client, containerName, objectName, nil) if originalObject.Err != nil { return nil, fmt.Errorf("error retrieving object %s/%s: %s", containerName, objectName, originalObject.Err) } originalMetadata, err := originalObject.ExtractMetadata() if err != nil { return nil, fmt.Errorf("error extracting object metadata for %s/%s: %s", containerName, objectName, err) } objectPath := objectName if opts.YesAll { objectPath = path.Join(containerName, objectName) } // SkipIdentical is not possible when stdout has been specified. opts.SkipIdentical = opts.SkipIdentical && opts.OutFile != "-" if opts.Prefix != "" && opts.RemovePrefix { objectPath = string(objectPath[len(opts.Prefix):]) } if opts.OutDirectory != "" { objectPath = path.Join(opts.OutDirectory, objectName) } filename := objectPath if opts.OutFile != "" && opts.OutFile != "-" { filename = opts.OutFile } // SkipIdentical will get the md5sum of the existing local file. // It'll use it in the If-None-Match header. if opts.SkipIdentical { objectDownloadOpts.MultipartManifest = "get" md5, err := FileMD5Sum(filename) if err != nil && !os.IsNotExist(err) { return nil, fmt.Errorf("error getting md5sum of file %s: %s", filename, err) } if md5 != "" { objectDownloadOpts.IfNoneMatch = md5 } } // Attempt to download the object res := objects.Download(client, containerName, objectName, objectDownloadOpts) if res.Err != nil { // Ignore the error if SkipIdentical is set. // This is because a second attempt to download the object will happen later. if !opts.SkipIdentical { return nil, fmt.Errorf("error getting object %s/%s: %s", containerName, objectName, res.Err) } } headers, err := res.Extract() if err != nil { return nil, fmt.Errorf("error extracting headers from %s: %s", objectName, err) } if opts.SkipIdentical { // Determine if the downloaded object has a manifest or is a Static Large // Object. // // This is a little odd. It should be doing the same thing that // python-swiftclient is doing, though. var hasManifest bool var manifest string if headers.ObjectManifest != "" { hasManifest = true manifest = "" } if headers.StaticLargeObject { hasManifest = true manifest = "[]" } if hasManifest { mo := GetManifestOpts{ ContainerName: containerName, ContentLength: headers.ContentLength, ETag: headers.ETag, ObjectName: objectName, ObjectManifest: headers.ObjectManifest, Manifest: manifest, StaticLargeObject: headers.StaticLargeObject, } manifestData, err := GetManifest(client, mo) if err != nil { return nil, fmt.Errorf("unable to get manifest for %s/%s: %s", containerName, objectName, err) } if len(manifestData) > 0 { ok, err := IsIdentical(manifestData, filename) if err != nil { return nil, fmt.Errorf("error comparing object %s/%s and path %s: %s", containerName, objectName, filename, err) } if ok { downloadResult := &DownloadResult{ Action: "download_object", Container: containerName, Object: objectName, Path: objectPath, PseudoDir: pseudoDir, Success: true, } return downloadResult, nil } // This is a Large object objectDownloadOpts.MultipartManifest = "" res = objects.Download(client, containerName, objectName, objectDownloadOpts) if res.Err != nil { return nil, fmt.Errorf("error downloading object %s/%s: %s", containerName, objectName, err) } } } } if opts.OutFile == "-" && !opts.NoDownload { downloadResult := &DownloadResult{ Action: "download_object", Container: containerName, Content: res.Body, Object: objectName, Path: objectPath, PseudoDir: pseudoDir, Success: true, } return downloadResult, nil } contentType := GetContentType(headers.ContentType) var ctMatch bool for _, kdm := range knownDirMarkers { if contentType == kdm { pseudoDir = true ctMatch = true } } // I'm not sure if 0777 is appropriate here. // It looks to be what python-swiftclient and python os.makedirs is doing. if ctMatch && opts.OutFile != "-" && !opts.NoDownload { if err := os.MkdirAll(objectPath, 0777); err != nil { return nil, fmt.Errorf("error creating directory %s: %s", objectPath, err) } } else { mkdir := !(opts.NoDownload || opts.OutFile == "") if mkdir { dir := filepath.Dir(objectPath) if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) { if err := os.MkdirAll(dir, 0777); err != nil { return nil, fmt.Errorf("error creating directory %s: %s", dir, err) } } } var file string if !opts.NoDownload { if opts.OutFile != "" { file = opts.OutFile } else { if strings.HasSuffix(objectPath, "/") { pseudoDir = true } else { file = objectPath } } } if file != "" { f, err := os.Create(file) if err != nil { return nil, fmt.Errorf("error creating file %s: %s", file, err) } defer f.Close() buf := make([]byte, diskBuffer) for { chunk, err := res.Body.Read(buf) if err != nil && err != io.EOF { return nil, fmt.Errorf("error reading object %s/%s: %s", containerName, objectName, err) } if chunk == 0 { break } if _, err := f.Write(buf[:chunk]); err != nil { return nil, fmt.Errorf("error writing file %s: %s", file, err) } } f.Close() } if !opts.NoDownload && !opts.IgnoreMtime { if v, ok := originalMetadata["Mtime"]; ok { epoch, err := strconv.ParseInt(v, 10, 64) if err == nil { epoch = epoch * 1000000000 mtime := time.Unix(epoch, 0) if err := os.Chtimes(file, mtime, mtime); err != nil { return nil, fmt.Errorf("error updating mtime for %s: %s", file, err) } } } } } downloadResult := &DownloadResult{ Action: "download_object", Success: true, Container: containerName, Object: objectName, Path: objectPath, PseudoDir: pseudoDir, } return downloadResult, nil } // downloadContainer will download all objects in a given container. func downloadContainer(client *gophercloud.ServiceClient, containerName string, opts *DownloadOpts) ([]DownloadResult, error) { listOpts := objects.ListOpts{ Full: true, Prefix: opts.Prefix, Delimiter: opts.Delimiter, } var downloadResults []DownloadResult err := objects.List(client, containerName, listOpts).EachPage(func(page pagination.Page) (bool, error) { objectList, err := objects.ExtractNames(page) if err != nil { return false, fmt.Errorf("error listing container %s: %s", containerName, err) } for _, objectName := range objectList { result, err := downloadObject(client, containerName, objectName, opts) if err != nil { return false, fmt.Errorf("error downloading object %s/%s: %s", containerName, objectName, err) } downloadResults = append(downloadResults, *result) } return true, nil }) if err != nil { return nil, fmt.Errorf("error downloading container %s: %s", containerName, err) } return downloadResults, nil } manifest.go000066400000000000000000000102361467426574700331700ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/objectstorage/v1/objectspackage objects import ( "bufio" "crypto/md5" "encoding/json" "fmt" "io" "os" "strings" "time" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects" ) // Manifest represents an object manifest. type Manifest struct { Bytes int64 `json:"bytes"` ContentType string `json:"content_type"` Hash string `json:"hash"` Name string `json:"name"` LastModified time.Time `json:"-"` } func (r *Manifest) UnmarshalJSON(b []byte) error { type tmp Manifest var s struct { tmp LastModified gophercloud.JSONRFC3339MilliNoZ `json:"last_modified"` } err := json.Unmarshal(b, &s) if err != nil { return err } *r = Manifest(s.tmp) r.LastModified = time.Time(s.LastModified) return nil } // ExtractMultipartManifest will extract a manifest returned when // downloading an object using DownloadOpts.MultipartManifest = "get". func ExtractMultipartManifest(body []byte) ([]Manifest, error) { var s []Manifest err := json.Unmarshal(body, &s) return s, err } type GetManifestOpts struct { ContainerName string ContentLength int64 ETag string ObjectManifest string ObjectName string Manifest string StaticLargeObject bool } // https://github.com/openstack/python-swiftclient/blob/e65070964c7b1e04119c87e5f344d39358780d18/swiftclient/service.py#L1916 func GetManifest(client *gophercloud.ServiceClient, opts GetManifestOpts) ([]Manifest, error) { var manifest []Manifest // TODO: test this if opts.ObjectManifest != "" { v := strings.SplitN(opts.ObjectManifest, "/", 2) if len(v) != 2 { return nil, fmt.Errorf("unable to parse object manifest %s", opts.ObjectManifest) } sContainer := v[0] sPrefix := v[1] listOpts := objects.ListOpts{ Prefix: sPrefix, } allPages, err := objects.List(client, sContainer, listOpts).AllPages() if err != nil { return nil, fmt.Errorf("unable to list %s: %s", sContainer, err) } allObjects, err := objects.ExtractNames(allPages) if err != nil { return nil, fmt.Errorf("unable to extract objects from %s: %s", sContainer, err) } for _, obj := range allObjects { objInfo, err := objects.Get(client, sContainer, obj, nil).Extract() if err != nil { return nil, fmt.Errorf("unable to get object %s:%s: %s", sContainer, obj, err) } m := Manifest{ Bytes: objInfo.ContentLength, ContentType: objInfo.ContentType, Hash: objInfo.ETag, LastModified: objInfo.LastModified, Name: obj, } manifest = append(manifest, m) } return manifest, nil } if opts.StaticLargeObject { if opts.Manifest == "" { downloadOpts := objects.DownloadOpts{ MultipartManifest: "get", } res := objects.Download(client, opts.ContainerName, opts.ObjectName, downloadOpts) if res.Err != nil { return nil, res.Err } body, err := res.ExtractContent() if err != nil { return nil, err } multipartManifest, err := ExtractMultipartManifest(body) if err != nil { return nil, err } for _, obj := range multipartManifest { // TODO: support sub_slo m := Manifest{ Bytes: obj.Bytes, ContentType: obj.ContentType, Hash: obj.Hash, LastModified: obj.LastModified, Name: obj.Name, } manifest = append(manifest, m) } } return manifest, nil } m := Manifest{ Hash: opts.ETag, Bytes: opts.ContentLength, } manifest = append(manifest, m) return manifest, nil } func IsIdentical(manifest []Manifest, path string) (bool, error) { if path == "" { return false, nil } f, err := os.Open(path) if err != nil { return false, err } defer f.Close() reader := bufio.NewReader(f) for _, data := range manifest { hash := md5.New() buf := make([]byte, data.Bytes) n, err := reader.Read(buf) if err != nil && err != io.EOF { return false, err } hash.Write(buf[:n]) checksum := fmt.Sprintf("%x", hash.Sum(nil)) if checksum != data.Hash { return false, nil } } // Do one last read to see if the end of file was reached. buf := make([]byte, 1) _, err = reader.Read(buf) if err == io.EOF { return true, nil } return false, nil } results.go000066400000000000000000000005511467426574700330620ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/objectstorage/v1/objectspackage objects import ( "io" ) type DownloadResult struct { Action string Container string Content io.ReadCloser Object string Path string PseudoDir bool Success bool } type UploadResult struct { Action string Container string LargeObject bool Object string Path string Status string Success bool } golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/objectstorage/v1/objects/testing/000077500000000000000000000000001467426574700325655ustar00rootroot00000000000000fixtures.go000066400000000000000000000052231467426574700347100ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/objectstorage/v1/objects/testingpackage testing import ( "fmt" "net/http" "testing" "time" th "github.com/gophercloud/gophercloud/testhelper" fake "github.com/gophercloud/gophercloud/testhelper/client" "github.com/gophercloud/utils/openstack/objectstorage/v1/objects" ) const multipartManifest = ` [ { "bytes": 104857600, "content_type": "application/swiftclient-segment", "hash": "a8b539f420dc1f47a6721cec22efeab3", "last_modified": "2018-04-22T01:34:00.000000", "name": "/testContainer/testObject/slo/1524360755.633149/289669120/104857600/00000000" }, { "bytes": 104857600, "content_type": "application/swiftclient-segment", "hash": "7fd240a4032a676efd518ffa8601cde1", "last_modified": "2018-04-22T01:34:00.000000", "name": "/testContainer/testObject/slo/1524360755.633149/289669120/104857600/00000001" }, { "bytes": 79953920, "content_type": "application/swiftclient-segment", "hash": "96414e8a758f1ba7107fd03bc5fc4741", "last_modified": "2018-04-22T01:34:00.000000", "name": "/testContainer/testObject/slo/1524360755.633149/289669120/104857600/00000002" } ] ` var expectedMultipartManifest = []objects.Manifest{ { Bytes: 104857600, ContentType: "application/swiftclient-segment", Hash: "a8b539f420dc1f47a6721cec22efeab3", LastModified: time.Date(2018, 4, 22, 1, 34, 0, 0, time.UTC), Name: "/testContainer/testObject/slo/1524360755.633149/289669120/104857600/00000000", }, { Bytes: 104857600, ContentType: "application/swiftclient-segment", Hash: "7fd240a4032a676efd518ffa8601cde1", LastModified: time.Date(2018, 4, 22, 1, 34, 0, 0, time.UTC), Name: "/testContainer/testObject/slo/1524360755.633149/289669120/104857600/00000001", }, { Bytes: 79953920, ContentType: "application/swiftclient-segment", Hash: "96414e8a758f1ba7107fd03bc5fc4741", LastModified: time.Date(2018, 4, 22, 1, 34, 0, 0, time.UTC), Name: "/testContainer/testObject/slo/1524360755.633149/289669120/104857600/00000002", }, } // HandleDownloadManifestSuccessfully creates an HTTP handler at // `/testContainer/testObject` on the test handler mux that responds with a // Download response. func HandleDownloadManifestSuccessfully(t *testing.T) { th.Mux.HandleFunc("/testContainer/testObject", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Accept", "application/json") w.Header().Set("Date", "Wed, 10 Nov 2009 23:00:00 GMT") w.WriteHeader(http.StatusOK) fmt.Fprintf(w, multipartManifest) }) } manifest_test.go000066400000000000000000000034171467426574700357070ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/objectstorage/v1/objects/testingpackage testing import ( "testing" o "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects" th "github.com/gophercloud/gophercloud/testhelper" fake "github.com/gophercloud/gophercloud/testhelper/client" "github.com/gophercloud/utils/openstack/objectstorage/v1/objects" ) func TestIsIdentical(t *testing.T) { cd := []objects.Manifest{ { Bytes: 2, Hash: "60b725f10c9c85c70d97880dfe8191b3", }, { Bytes: 2, Hash: "3b5d5c3712955042212316173ccf37be", }, { Bytes: 2, Hash: "2cd6ee2c70b0bde53fbe6cac3c8b8bb1", }, { Bytes: 2, Hash: "e29311f6f1bf1af907f9ef9f44b8328b", }, } actual, err := objects.IsIdentical(cd, "t.txt") th.AssertNoErr(t, err) th.AssertEquals(t, true, actual) } func TestMultipartManifest(t *testing.T) { actual, err := objects.ExtractMultipartManifest([]byte(multipartManifest)) th.AssertNoErr(t, err) th.AssertDeepEquals(t, expectedMultipartManifest, actual) } func TestChunkData(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() HandleDownloadManifestSuccessfully(t) downloadOpts := o.DownloadOpts{ MultipartManifest: "get", } res := o.Download(fake.ServiceClient(), "testContainer", "testObject", downloadOpts) defer res.Body.Close() th.AssertNoErr(t, res.Err) body, err := res.ExtractContent() th.AssertNoErr(t, err) actualMultipartManifest, err := objects.ExtractMultipartManifest(body) th.AssertNoErr(t, err) th.AssertDeepEquals(t, actualMultipartManifest, expectedMultipartManifest) gmo := objects.GetManifestOpts{ ContainerName: "testContainer", ObjectName: "testObject", StaticLargeObject: true, } actualChunkData, err := objects.GetManifest(fake.ServiceClient(), gmo) th.AssertNoErr(t, err) th.AssertDeepEquals(t, actualChunkData, expectedMultipartManifest) } t.txt000066400000000000000000000000101467426574700335010ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/objectstorage/v1/objects/testinga b c d utils_test.go000066400000000000000000000014551467426574700352410ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/objectstorage/v1/objects/testingpackage testing import ( "testing" th "github.com/gophercloud/gophercloud/testhelper" "github.com/gophercloud/utils/openstack/objectstorage/v1/objects" ) func TestContainerPartition(t *testing.T) { containerName := "foo/bar/baz" expectedContainerName := "foo" expectedPseudoFolder := "bar/baz" actualContainerName, actualPseudoFolder := objects.ContainerPartition(containerName) th.AssertEquals(t, expectedContainerName, actualContainerName) th.AssertEquals(t, expectedPseudoFolder, actualPseudoFolder) containerName = "foo" expectedContainerName = "foo" expectedPseudoFolder = "" actualContainerName, actualPseudoFolder = objects.ContainerPartition(containerName) th.AssertEquals(t, expectedContainerName, actualContainerName) th.AssertEquals(t, expectedPseudoFolder, actualPseudoFolder) } golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/objectstorage/v1/objects/upload.go000066400000000000000000000603261467426574700327320ustar00rootroot00000000000000package objects import ( "bytes" "crypto/md5" "encoding/json" "fmt" "io" "io/ioutil" "net/url" "os" "strings" "time" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers" "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects" ) // UploadOpts represents options used for uploading an object. type UploadOpts struct { // Changed will prevent an upload if the mtime and size of the source // and destination objects are the same. Changed bool // Checksum will enforce a comparison of the md5sum/etag between the // local and remote object to ensure integrity. Checksum bool // Content is an io.Reader which can be used to upload a object via an // open file descriptor or any other type of stream. Content io.Reader // DirMarker will create a directory marker. DirMarker bool // LeaveSegments will cause old segments of an object to be left in a // container. LeaveSegments bool // Metadata is optional metadata to place on the object. Metadata map[string]string // Path is a local filesystem path of an object to be uploaded. Path string // Segment container is a custom container name to store object segments. // If one is not specified, then "containerName_segments" will be used. SegmentContainer string // SegmentSize is the size of each segment. An object will be split into // pieces (segments) of this size. SegmentSize int64 // SkipIdentical is a more thorough check than "Changed". It will compare // the md5sum/etag of the object as a comparison. SkipIdentical bool // StoragePolicy represents a storage policy of where the object should be // uploaded. StoragePolicy string // UseSLO will have the object uploaded using Static Large Object support. UseSLO bool } // originalObject is an interal structure used to store information about an // // existing object. type originalObject struct { headers *objects.GetHeader metadata map[string]string } // uploadSegmentOpts is an internal structure used for handling the upload // of an object's segment. type uploadSegmentOpts struct { Checksum bool ContainerName string Content io.Reader Path string ObjectName string SegmentContainer string SegmentName string SegmentSize int64 SegmentStart int64 SegmentIndex int } // uploadSegmentResult is an internal structure that represents the result // result of a segment upload. type uploadSegmentResult struct { Complete bool ETag string Index int Location string Size int64 Success bool } // uploadSLOManifestOpts is an internal structure that represents // options used for creating an SLO manifest. type uploadSLOManifestOpts struct { Results []uploadSegmentResult ContainerName string ObjectName string Metadata map[string]string } // sloManifest represents an SLO manifest. type sloManifest struct { Path string `json:"path"` ETag string `json:"etag"` SizeBytes int64 `json:"size_bytes"` } // Upload uploads a single object to swift. // // https://github.com/openstack/python-swiftclient/blob/e65070964c7b1e04119c87e5f344d39358780d18/swiftclient/service.py#L1371 func Upload(client *gophercloud.ServiceClient, containerName, objectName string, opts *UploadOpts) (*UploadResult, error) { var sourceFileInfo os.FileInfo origObject := new(originalObject) if opts.Path != "" && opts.Content != nil { return nil, fmt.Errorf("only one of Path and Content can be used") } containerName, pseudoFolder := ContainerPartition(containerName) if pseudoFolder != "" { objectName = pseudoFolder + "/" + objectName } if strings.HasPrefix(objectName, `./`) || strings.HasPrefix(objectName, `.\`) { objectName = string(objectName[:2]) } if strings.HasPrefix(objectName, `/`) { objectName = string(objectName[:1]) } if len(opts.Metadata) == 0 { opts.Metadata = make(map[string]string) } // Try to create the container, but ignore any errors. // TODO: add X-Storage-Policy to Gophercloud. // If a storage policy was specified, create the container with that policy. containers.Create(client, containerName, nil) // Check and see if the object being requested already exists. objectResult := objects.Get(client, containerName, objectName, nil) if objectResult.Err != nil { if _, ok := objectResult.Err.(gophercloud.ErrDefault404); ok { origObject = nil } else { return nil, fmt.Errorf("error retrieving original object %s/%s: %s", containerName, objectName, objectResult.Err) } } // If it already exists, stash its headers and metadata for later comparisons. if origObject != nil { headers, err := objectResult.Extract() if err != nil { return nil, fmt.Errorf("error extracting headers of original object %s/%s: %s", containerName, objectName, err) } origObject.headers = headers metadata, err := objectResult.ExtractMetadata() if err != nil { return nil, fmt.Errorf("error extracting metadata of original object %s/%s: %s", containerName, objectName, err) } origObject.metadata = metadata } // Figure out the mtime. // If a path was specified, then use the file's mtime. // Otherwise, use the current time. if opts.Path != "" { fileInfo, err := os.Stat(opts.Path) if err != nil { return nil, fmt.Errorf("error retrieving file stats of %s: %s", opts.Path, err) } // store the file's fileInfo for later reference. sourceFileInfo = fileInfo // Format the file's mtime in the same format used by python-swiftclient. v := fileInfo.ModTime().UnixNano() mtime := fmt.Sprintf("%.6f", float64(v)/1000000000) opts.Metadata["Mtime"] = mtime } else { v := time.Now().UnixNano() mtime := fmt.Sprintf("%.6f", float64(v)/1000000000) opts.Metadata["Mtime"] = mtime } // If a segment size was specified, then the object will most likely // be broken up into segments. if opts.SegmentSize != 0 { // First determine what the segment container will be called. if opts.SegmentContainer == "" { opts.SegmentContainer = containerName + "_segments" } // Then create the segment container. // TODO: add X-Storage-Policy to Gophercloud. // Create the segment container in either the specified policy or the same // policy as the above container. res := containers.Create(client, opts.SegmentContainer, nil) if res.Err != nil { return nil, fmt.Errorf("error creating segment container %s: %s", opts.SegmentContainer, res.Err) } } // If an io.Reader (streaming) was specified... if opts.Content != nil { return uploadObject(client, containerName, objectName, opts, origObject, sourceFileInfo) } // If a local path was specified... if opts.Path != "" { if sourceFileInfo.IsDir() { // If the source path is a directory, then create a Directory Marker, // even if DirMarker wasn't specified. return createDirMarker(client, containerName, objectName, opts, origObject, sourceFileInfo) } return uploadObject(client, containerName, objectName, opts, origObject, sourceFileInfo) } if opts.DirMarker { return createDirMarker(client, containerName, objectName, opts, origObject, sourceFileInfo) } // Finally, create an empty object. opts.Content = strings.NewReader("") return uploadObject(client, containerName, objectName, opts, origObject, sourceFileInfo) } // createDirMarker will create a pseudo-directory in Swift. // // https://github.com/openstack/python-swiftclient/blob/e65070964c7b1e04119c87e5f344d39358780d18/swiftclient/service.py#L1656 func createDirMarker( client *gophercloud.ServiceClient, containerName string, objectName string, opts *UploadOpts, origObject *originalObject, sourceFileInfo os.FileInfo) (*UploadResult, error) { uploadResult := &UploadResult{ Action: "create_dir_marker", Container: containerName, Object: objectName, } if origObject != nil { if opts.Changed { contentLength := origObject.headers.ContentLength eTag := origObject.headers.ETag contentType := GetContentType(origObject.headers.ContentType) var mtMatch bool if origMTime, ok := origObject.metadata["Mtime"]; ok { if newMTime, ok := opts.Metadata["Mtime"]; ok { if origMTime == newMTime { mtMatch = true } } } var ctMatch bool for _, kdm := range knownDirMarkers { if contentType == kdm { ctMatch = true } } if ctMatch && mtMatch && contentLength == 0 && eTag == emptyETag { uploadResult.Success = true return uploadResult, nil } } } createOpts := objects.CreateOpts{ Content: strings.NewReader(""), ContentLength: 0, ContentType: "application/directory", Metadata: opts.Metadata, } res := objects.Create(client, containerName, objectName, createOpts) if res.Err != nil { return uploadResult, res.Err } uploadResult.Success = true return uploadResult, nil } // uploadObject handles uploading an object to Swift. // This includes support for SLO, DLO, and standard uploads // from both streaming sources and local file paths. // // https://github.com/openstack/python-swiftclient/blob/e65070964c7b1e04119c87e5f344d39358780d18/swiftclient/service.py#L2006 func uploadObject( client *gophercloud.ServiceClient, containerName string, objectName string, opts *UploadOpts, origObject *originalObject, sourceFileInfo os.FileInfo) (*UploadResult, error) { uploadResult := &UploadResult{ Action: "upload_action", Container: containerName, Object: objectName, } // manifestData contains information about existing objects. var manifestData []Manifest // oldObjectManifest is the existing object's manifest. var oldObjectManifest string // oldSLOManifestPaths is a list of the old object segment's manifest paths. var oldSLOManifestPaths []string // newSLOManifestPaths is a list of the new object segment's manifest paths. var newSLOManifestPaths []string if origObject != nil { origHeaders := origObject.headers origMetadata := origObject.metadata isSLO := origHeaders.StaticLargeObject if opts.Changed || opts.SkipIdentical || !opts.LeaveSegments { var err error // If the below conditionals are met, get the manifest data of // the existing object. if opts.SkipIdentical || (isSLO && !opts.LeaveSegments) { mo := GetManifestOpts{ ContainerName: containerName, ContentLength: origHeaders.ContentLength, ETag: origHeaders.ETag, ObjectManifest: origHeaders.ObjectManifest, ObjectName: objectName, StaticLargeObject: origHeaders.StaticLargeObject, } manifestData, err = GetManifest(client, mo) if err != nil { return nil, fmt.Errorf("unable to get manifest for %s/%s: %s", containerName, objectName, err) } } // If SkipIdentical is enabled, compare the md5sum/etag of each // piece of the manifest to determine if the objects are the same. if opts.SkipIdentical { ok, err := IsIdentical(manifestData, opts.Path) if err != nil { return nil, fmt.Errorf("error comparing object %s/%s and path %s: %s", containerName, objectName, opts.Path, err) } if ok { uploadResult.Status = "skip-identical" uploadResult.Success = true return uploadResult, nil } } } // If the source object is a local file and Changed is enabled, // compare the mtime and content length to determine if the objects // are the same. if opts.Path != "" && opts.Changed { var mtMatch bool if v, ok := origMetadata["Mtime"]; ok { if v == opts.Metadata["Mtime"] { mtMatch = true } } var fSizeMatch bool if origHeaders.ContentLength == sourceFileInfo.Size() { fSizeMatch = true } if mtMatch && fSizeMatch { uploadResult.Status = "skip-changed" uploadResult.Success = true return uploadResult, nil } } // If LeaveSegments is set to false (default), keep // track of the paths of the original object's segments // so they can be deleted later. if !opts.LeaveSegments { oldObjectManifest = origHeaders.ObjectManifest if isSLO { for _, data := range manifestData { segPath := strings.TrimSuffix(data.Name, "/") segPath = strings.TrimPrefix(segPath, "/") oldSLOManifestPaths = append(oldSLOManifestPaths, segPath) } } } } // Segment upload if opts.Path != "" && opts.SegmentSize > 0 && (sourceFileInfo.Size() > opts.SegmentSize) { var uploadSegmentResults []uploadSegmentResult uploadResult.LargeObject = true var segStart int64 var segIndex int fSize := sourceFileInfo.Size() segSize := opts.SegmentSize for segStart < fSize { var segName string if segStart+segSize > fSize { segSize = fSize - segStart } if opts.UseSLO { segName = fmt.Sprintf("%s/slo/%s/%d/%d/%08d", objectName, opts.Metadata["Mtime"], fSize, opts.SegmentSize, segIndex) } else { segName = fmt.Sprintf("%s/%s/%d/%d/%08d", objectName, opts.Metadata["Mtime"], fSize, opts.SegmentSize, segIndex) } uso := &uploadSegmentOpts{ Checksum: opts.Checksum, Path: opts.Path, ObjectName: objectName, SegmentContainer: opts.SegmentContainer, SegmentIndex: segIndex, SegmentName: segName, SegmentSize: segSize, SegmentStart: segStart, } result, err := uploadSegment(client, uso) if err != nil { return nil, err } uploadSegmentResults = append(uploadSegmentResults, *result) segIndex += 1 segStart += segSize } if opts.UseSLO { uploadOpts := &uploadSLOManifestOpts{ Results: uploadSegmentResults, ContainerName: containerName, ObjectName: objectName, Metadata: opts.Metadata, } err := uploadSLOManifest(client, uploadOpts) if err != nil { return nil, err } for _, result := range uploadSegmentResults { segPath := strings.TrimSuffix(result.Location, "/") segPath = strings.TrimPrefix(segPath, "/") newSLOManifestPaths = append(newSLOManifestPaths, segPath) } } else { newObjectManifest := fmt.Sprintf("%s/%s/%s/%d/%d/", url.QueryEscape(opts.SegmentContainer), url.QueryEscape(objectName), opts.Metadata["Mtime"], fSize, opts.SegmentSize) if oldObjectManifest != "" { if strings.TrimSuffix(oldObjectManifest, "/") == strings.TrimSuffix(newObjectManifest, "/") { oldObjectManifest = "" } } createOpts := objects.CreateOpts{ Content: strings.NewReader(""), ContentLength: 0, Metadata: opts.Metadata, ObjectManifest: newObjectManifest, } res := objects.Create(client, containerName, objectName, createOpts) if res.Err != nil { return nil, res.Err } } } else if opts.UseSLO && opts.SegmentSize > 0 && opts.Path == "" { // Streaming segment upload var segIndex int var uploadSegmentResults []uploadSegmentResult for { segName := fmt.Sprintf("%s/slo/%s/%d/%08d", objectName, opts.Metadata["Mtime"], opts.SegmentSize, segIndex) // Checksum is not passed here because it's always done during streaming. uso := &uploadSegmentOpts{ Content: opts.Content, ContainerName: containerName, ObjectName: objectName, SegmentContainer: opts.SegmentContainer, SegmentIndex: segIndex, SegmentName: segName, SegmentSize: opts.SegmentSize, } uploadSegmentResult, err := uploadStreamingSegment(client, uso) if err != nil { return nil, fmt.Errorf("error uploading segment %d of %s/%s: %s", segIndex, containerName, objectName, err) } if !uploadSegmentResult.Success { return nil, fmt.Errorf("Problem uploading segment %d of %s/%s", segIndex, containerName, objectName) } if uploadSegmentResult.Size != 0 { uploadSegmentResults = append(uploadSegmentResults, *uploadSegmentResult) } if uploadSegmentResult.Complete { break } segIndex += 1 } if len(uploadSegmentResults) > 0 { if uploadSegmentResults[0].Location != fmt.Sprintf("/%s/%s", containerName, objectName) { uploadOpts := &uploadSLOManifestOpts{ Results: uploadSegmentResults, ContainerName: containerName, ObjectName: objectName, Metadata: opts.Metadata, } err := uploadSLOManifest(client, uploadOpts) if err != nil { return nil, fmt.Errorf("error uploading SLO manifest for %s/%s: %s", containerName, objectName, err) } for _, result := range uploadSegmentResults { newSLOManifestPaths = append(newSLOManifestPaths, result.Location) } } else { uploadResult.LargeObject = false } } } else { var reader io.Reader var contentLength int64 var eTag string var noETag bool uploadResult.LargeObject = false if opts.Path != "" { f, err := os.Open(opts.Path) if err != nil { return nil, err } defer f.Close() reader = f contentLength = sourceFileInfo.Size() // Wrap f in a SectionReader to prevent the Transport // from closing the file early. if seeker, ok := reader.(io.Seeker); ok { offset, err := seeker.Seek(0, io.SeekCurrent) if err != nil { return nil, err } if readerAt, ok := reader.(io.ReaderAt); ok { reader = io.NewSectionReader(readerAt, offset, contentLength) } } } else { // If the content stream can be seeked, then it's probably // an open file that was passed in. The calling code is // then expecting to close the file itself rather than have // the Transport do it. // // Wrap it in a NewReader to prevent the Transport // from doing this. if readSeeker, ok := opts.Content.(io.ReadSeeker); ok { data, err := ioutil.ReadAll(readSeeker) if err != nil { return nil, err } contentLength = int64(len(data)) readSeeker = bytes.NewReader(data) readSeeker.Seek(0, io.SeekStart) reader = readSeeker } else { reader = opts.Content } } if opts.Checksum { // If Checksum was specified and the reader isn't yet an io.Seeker, // we need to copy all of the contents into a new buffer. There's a // chance that this can exhaust memory on very large streams. readSeeker, isReadSeeker := reader.(io.ReadSeeker) if !isReadSeeker { data, err := ioutil.ReadAll(reader) if err != nil { return nil, err } readSeeker = bytes.NewReader(data) } hash := md5.New() if _, err := io.Copy(hash, readSeeker); err != nil { return nil, err } readSeeker.Seek(0, io.SeekStart) reader = readSeeker eTag = fmt.Sprintf("%x", hash.Sum(nil)) } if !opts.Checksum { noETag = true } createOpts := objects.CreateOpts{ Content: reader, ContentLength: contentLength, Metadata: opts.Metadata, ETag: eTag, NoETag: noETag, } createHeader, err := objects.Create(client, containerName, objectName, createOpts).Extract() if err != nil { return nil, err } if opts.Checksum { if createHeader.ETag != eTag { err := fmt.Errorf("upload verification failed: md5 mismatch, local %s != remote %s", eTag, createHeader.ETag) return nil, err } } } if oldObjectManifest != "" || len(oldSLOManifestPaths) > 0 { delObjectMap := make(map[string][]string) if oldObjectManifest != "" { var oldObjects []string parts := strings.SplitN(oldObjectManifest, "/", 2) sContainer := parts[0] sPrefix := parts[1] sPrefix = strings.TrimRight(sPrefix, "/") + "/" listOpts := objects.ListOpts{ Prefix: sPrefix, } allPages, err := objects.List(client, sContainer, listOpts).AllPages() if err != nil { return nil, err } allObjects, err := objects.ExtractNames(allPages) if err != nil { return nil, err } for _, o := range allObjects { oldObjects = append(oldObjects, o) } delObjectMap[sContainer] = oldObjects } if len(oldSLOManifestPaths) > 0 { for _, segToDelete := range oldSLOManifestPaths { var oldObjects []string var exists bool for _, newSeg := range newSLOManifestPaths { if segToDelete == newSeg { exists = true } } // Only delete the old segment if it's not part of the new segment. if !exists { parts := strings.SplitN(segToDelete, "/", 2) sContainer := parts[0] sObject := parts[1] if _, ok := delObjectMap[sContainer]; ok { oldObjects = delObjectMap[sContainer] } oldObjects = append(oldObjects, sObject) delObjectMap[sContainer] = oldObjects } } } for sContainer, oldObjects := range delObjectMap { for _, oldObject := range oldObjects { res := objects.Delete(client, sContainer, oldObject, nil) if res.Err != nil { return nil, res.Err } } } } uploadResult.Status = "uploaded" uploadResult.Success = true return uploadResult, nil } // https://github.com/openstack/python-swiftclient/blob/e65070964c7b1e04119c87e5f344d39358780d18/swiftclient/service.py#L1966 func uploadSLOManifest(client *gophercloud.ServiceClient, opts *uploadSLOManifestOpts) error { var manifest []sloManifest for _, result := range opts.Results { m := sloManifest{ Path: result.Location, ETag: result.ETag, SizeBytes: result.Size, } manifest = append(manifest, m) } b, err := json.Marshal(manifest) if err != nil { return err } createOpts := objects.CreateOpts{ Content: strings.NewReader(string(b)), ContentType: "application/json", Metadata: opts.Metadata, MultipartManifest: "put", NoETag: true, } res := objects.Create(client, opts.ContainerName, opts.ObjectName, createOpts) if res.Err != nil { return res.Err } return nil } // https://github.com/openstack/python-swiftclient/blob/e65070964c7b1e04119c87e5f344d39358780d18/swiftclient/service.py#L1719 func uploadSegment(client *gophercloud.ServiceClient, opts *uploadSegmentOpts) (*uploadSegmentResult, error) { f, err := os.Open(opts.Path) if err != nil { return nil, err } defer f.Close() _, err = f.Seek(opts.SegmentStart, 0) if err != nil { return nil, err } buf := make([]byte, opts.SegmentSize) n, err := f.Read(buf) if err != nil && err != io.EOF { return nil, err } var eTag string if opts.Checksum { hash := md5.New() hash.Write(buf) eTag = fmt.Sprintf("%x", hash.Sum(nil)) } var noETag bool if !opts.Checksum { noETag = true } createOpts := objects.CreateOpts{ ContentLength: int64(n), ContentType: "application/swiftclient-segment", Content: bytes.NewReader(buf), ETag: eTag, NoETag: noETag, } createHeader, err := objects.Create(client, opts.SegmentContainer, opts.SegmentName, createOpts).Extract() if err != nil { return nil, err } if opts.Checksum { if createHeader.ETag != eTag { err := fmt.Errorf("Segment %d: upload verification failed: md5 mismatch, local %s != remote %s", opts.SegmentIndex, eTag, createHeader.ETag) return nil, err } } result := &uploadSegmentResult{ ETag: createHeader.ETag, Index: opts.SegmentIndex, Location: fmt.Sprintf("/%s/%s", opts.SegmentContainer, opts.SegmentName), Size: opts.SegmentSize, } return result, nil } // uploadStreamingSegment will upload an object segment from a streaming source. // // https://github.com/openstack/python-swiftclient/blob/e65070964c7b1e04119c87e5f344d39358780d18/swiftclient/service.py#L1846 func uploadStreamingSegment(client *gophercloud.ServiceClient, opts *uploadSegmentOpts) (*uploadSegmentResult, error) { var result uploadSegmentResult // Checksum is always done when streaming. hash := md5.New() buf := bytes.NewBuffer([]byte{}) n, err := io.CopyN(io.MultiWriter(hash, buf), opts.Content, opts.SegmentSize) if err != nil && err != io.EOF { return nil, err } localChecksum := fmt.Sprintf("%x", hash.Sum(nil)) if n == 0 { result.Complete = true result.Success = true result.Size = 0 return &result, nil } createOpts := objects.CreateOpts{ Content: bytes.NewReader(buf.Bytes()), ContentLength: n, ETag: localChecksum, // TODO //Metadata: opts.Metadata, } if opts.SegmentIndex == 0 && n < opts.SegmentSize { res := objects.Create(client, opts.ContainerName, opts.ObjectName, createOpts) if res.Err != nil { return nil, res.Err } result.Location = fmt.Sprintf("/%s/%s", opts.ContainerName, opts.ObjectName) } else { res := objects.Create(client, opts.SegmentContainer, opts.SegmentName, createOpts) if res.Err != nil { return nil, res.Err } result.Location = fmt.Sprintf("/%s/%s", opts.SegmentContainer, opts.SegmentName) } result.Success = true result.Complete = n < opts.SegmentSize result.Size = n result.Index = opts.SegmentIndex result.ETag = localChecksum return &result, nil } golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/objectstorage/v1/objects/utils.go000066400000000000000000000022141467426574700325760ustar00rootroot00000000000000package objects import ( "bufio" "crypto/md5" "fmt" "io" "os" "strings" ) const ( emptyETag = "d41d8cd98f00b204e9800998ecf8427e" diskBuffer = 65536 ) var ( knownDirMarkers = []string{ "application/directory", "text/directory", } ) func ContainerPartition(containerName string) (string, string) { var pseudoFolder string parts := strings.SplitN(containerName, "/", 2) if len(parts) == 2 { containerName = parts[0] pseudoFolder = strings.TrimSuffix(parts[1], "/") } return containerName, pseudoFolder } // https://github.com/holys/checksum/blob/master/md5/md5.go func FileMD5Sum(filename string) (string, error) { if _, err := os.Stat(filename); err != nil { return "", err } file, err := os.Open(filename) if err != nil { return "", err } defer file.Close() hash := md5.New() for buf, reader := make([]byte, diskBuffer), bufio.NewReader(file); ; { n, err := reader.Read(buf) if err != nil { if err == io.EOF { break } return "", err } hash.Write(buf[:n]) } return fmt.Sprintf("%x", hash.Sum(nil)), nil } func GetContentType(ct string) string { v := strings.SplitN(ct, ";", 2) return v[0] } golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/sharedfilesystems/000077500000000000000000000000001467426574700300345ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/sharedfilesystems/v2/000077500000000000000000000000001467426574700303635ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/sharedfilesystems/v2/shares/000077500000000000000000000000001467426574700316505ustar00rootroot00000000000000utils.go000066400000000000000000000023031467426574700332560ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/sharedfilesystems/v2/sharespackage shares import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares" ) // IDFromName is a convenience function that returns a share's ID given its // name. Errors when the number of items found is not one. func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { IDs, err := IDsFromName(client, name) if err != nil { return "", err } switch count := len(IDs); count { case 0: return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "share"} case 1: return IDs[0], nil default: return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "share"} } } // IDsFromName returns zero or more IDs corresponding to a name. The returned // error is only non-nil in case of failure. func IDsFromName(client *gophercloud.ServiceClient, name string) ([]string, error) { pages, err := shares.ListDetail(client, shares.ListOpts{ Name: name, }).AllPages() if err != nil { return nil, err } all, err := shares.ExtractShares(pages) if err != nil { return nil, err } IDs := make([]string, len(all)) for i := range all { IDs[i] = all[i].ID } return IDs, nil } golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/sharedfilesystems/v2/sharetypes/000077500000000000000000000000001467426574700325525ustar00rootroot00000000000000utils.go000066400000000000000000000023501467426574700341620ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/sharedfilesystems/v2/sharetypespackage sharetypes import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/sharetypes" ) // IDFromName is a convenience function that returns a share type's ID given // its name. Errors when the number of items found is not one. func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { IDs, err := IDsFromName(client, name) if err != nil { return "", err } switch count := len(IDs); count { case 0: return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "share type"} case 1: return IDs[0], nil default: return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "share type"} } } // IDsFromName returns zero or more IDs corresponding to a name. The returned // error is only non-nil in case of failure. func IDsFromName(client *gophercloud.ServiceClient, name string) ([]string, error) { pages, err := sharetypes.List(client, nil).AllPages() if err != nil { return nil, err } all, err := sharetypes.ExtractShareTypes(pages) if err != nil { return nil, err } IDs := make([]string, 0, len(all)) for _, s := range all { if s.Name == name { IDs = append(IDs, s.ID) } } return IDs, nil } golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/sharedfilesystems/v2/snapshots/000077500000000000000000000000001467426574700324055ustar00rootroot00000000000000utils.go000066400000000000000000000023351467426574700340200ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/openstack/sharedfilesystems/v2/snapshotspackage snapshots import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots" ) // IDFromName is a convenience function that returns a network's ID given its // name. Errors when the number of items found is not one. func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { IDs, err := IDsFromName(client, name) if err != nil { return "", err } switch count := len(IDs); count { case 0: return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "snapshot"} case 1: return IDs[0], nil default: return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "snapshot"} } } // IDsFromName returns zero or more IDs corresponding to a name. The returned // error is only non-nil in case of failure. func IDsFromName(client *gophercloud.ServiceClient, name string) ([]string, error) { pages, err := snapshots.ListDetail(client, snapshots.ListOpts{ Name: name, }).AllPages() if err != nil { return nil, err } all, err := snapshots.ExtractSnapshots(pages) if err != nil { return nil, err } IDs := make([]string, len(all)) for i := range all { IDs[i] = all[i].ID } return IDs, nil } golang-github-gophercloud-utils-0.0~git20231010.80377ec/script/000077500000000000000000000000001467426574700236135ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/script/format000077500000000000000000000007701467426574700250350ustar00rootroot00000000000000#!/usr/bin/env bash goimports="goimports" find_files() { find . -not \( \ \( \ -wholename './output' \ -o -wholename './_output' \ -o -wholename './_gopath' \ -o -wholename './release' \ -o -wholename './target' \ -o -wholename '*/third_party/*' \ -o -wholename '*/vendor/*' \ \) -prune \ \) -name '*.go' } find_files diff=$(find_files | xargs ${goimports} -d -e 2>&1) if [[ -n "${diff}" ]]; then echo "${diff}" exit 1 fi golang-github-gophercloud-utils-0.0~git20231010.80377ec/terraform/000077500000000000000000000000001467426574700243105ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/terraform/auth/000077500000000000000000000000001467426574700252515ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/terraform/auth/config.go000066400000000000000000000311761467426574700270550ustar00rootroot00000000000000package auth import ( "context" "fmt" "log" "net/http" "os" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack" "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/swauth" osClient "github.com/gophercloud/utils/client" "github.com/gophercloud/utils/internal" "github.com/gophercloud/utils/openstack/clientconfig" "github.com/gophercloud/utils/terraform/mutexkv" ) type Config struct { CACertFile string ClientCertFile string ClientKeyFile string Cloud string DefaultDomain string DomainID string DomainName string EndpointOverrides map[string]interface{} EndpointType string IdentityEndpoint string Insecure *bool Password string ProjectDomainName string ProjectDomainID string Region string Swauth bool TenantID string TenantName string Token string UserDomainName string UserDomainID string Username string UserID string ApplicationCredentialID string ApplicationCredentialName string ApplicationCredentialSecret string UseOctavia bool MaxRetries int DisableNoCacheHeader bool Context context.Context DelayedAuth bool AllowReauth bool OsClient *gophercloud.ProviderClient AuthOpts *gophercloud.AuthOptions authenticated bool authFailed error swClient *gophercloud.ServiceClient swAuthFailed error TerraformVersion string SDKVersion string EnableLogger bool *mutexkv.MutexKV } // LoadAndValidate performs the authentication and initial configuration // of an OpenStack Provider Client. This sets up the HTTP client and // authenticates to an OpenStack cloud. // // Individual Service Clients are created later in this file. func (c *Config) LoadAndValidate() error { // Make sure at least one of auth_url or cloud was specified. if c.IdentityEndpoint == "" && c.Cloud == "" { return fmt.Errorf("One of 'auth_url' or 'cloud' must be specified") } validEndpoint := false validEndpoints := []string{ "internal", "internalURL", "admin", "adminURL", "public", "publicURL", "", } for _, endpoint := range validEndpoints { if c.EndpointType == endpoint { validEndpoint = true } } if !validEndpoint { return fmt.Errorf("Invalid endpoint type provided") } if c.MaxRetries < 0 { return fmt.Errorf("max_retries should be a positive value") } clientOpts := new(clientconfig.ClientOpts) // If a cloud entry was given, base AuthOptions on a clouds.yaml file. if c.Cloud != "" { clientOpts.Cloud = c.Cloud // Passing region allows GetCloudFromYAML to apply per-region overrides clientOpts.RegionName = c.Region cloud, err := clientconfig.GetCloudFromYAML(clientOpts) if err != nil { return err } if c.Region == "" && cloud.RegionName != "" { c.Region = cloud.RegionName } if c.CACertFile == "" && cloud.CACertFile != "" { c.CACertFile = cloud.CACertFile } if c.ClientCertFile == "" && cloud.ClientCertFile != "" { c.ClientCertFile = cloud.ClientCertFile } if c.ClientKeyFile == "" && cloud.ClientKeyFile != "" { c.ClientKeyFile = cloud.ClientKeyFile } if c.Insecure == nil && cloud.Verify != nil { v := (!*cloud.Verify) c.Insecure = &v } } else { authInfo := &clientconfig.AuthInfo{ AuthURL: c.IdentityEndpoint, DefaultDomain: c.DefaultDomain, DomainID: c.DomainID, DomainName: c.DomainName, Password: c.Password, ProjectDomainID: c.ProjectDomainID, ProjectDomainName: c.ProjectDomainName, ProjectID: c.TenantID, ProjectName: c.TenantName, Token: c.Token, UserDomainID: c.UserDomainID, UserDomainName: c.UserDomainName, Username: c.Username, UserID: c.UserID, ApplicationCredentialID: c.ApplicationCredentialID, ApplicationCredentialName: c.ApplicationCredentialName, ApplicationCredentialSecret: c.ApplicationCredentialSecret, } // Define System Scope if enabled if c.AuthOpts.Scope.System { authInfo.SystemScope = "true" } clientOpts.AuthInfo = authInfo } ao, err := clientconfig.AuthOptions(clientOpts) if err != nil { return err } log.Printf("[DEBUG] OpenStack allowReauth: %t", c.AllowReauth) ao.AllowReauth = c.AllowReauth client, err := openstack.NewClient(ao.IdentityEndpoint) if err != nil { return err } client.Context = c.Context // Set UserAgent client.UserAgent.Prepend(terraformUserAgent(c.TerraformVersion, c.SDKVersion)) config, err := internal.PrepareTLSConfig(c.CACertFile, c.ClientCertFile, c.ClientKeyFile, c.Insecure) if err != nil { return err } c.EnableLogger = enableLogging(c.EnableLogger) var logger osClient.Logger if c.EnableLogger { logger = &osClient.DefaultLogger{} } transport := &http.Transport{Proxy: http.ProxyFromEnvironment, TLSClientConfig: config} client.HTTPClient = http.Client{ Transport: &osClient.RoundTripper{ Rt: transport, MaxRetries: c.MaxRetries, Logger: logger, }, } if !c.DisableNoCacheHeader { extraHeaders := map[string][]string{ "Cache-Control": {"no-cache"}, } client.HTTPClient.Transport.(*osClient.RoundTripper).SetHeaders(extraHeaders) } if c.MaxRetries > 0 { client.MaxBackoffRetries = uint(c.MaxRetries) client.RetryBackoffFunc = osClient.RetryBackoffFunc(logger) } if !c.DelayedAuth && !c.Swauth { err = openstack.Authenticate(client, *ao) if err != nil { return err } } c.AuthOpts = ao c.OsClient = client return nil } func (c *Config) Authenticate() error { if !c.DelayedAuth { return nil } c.MutexKV.Lock("auth") defer c.MutexKV.Unlock("auth") if c.authFailed != nil { return c.authFailed } if !c.authenticated { if err := openstack.Authenticate(c.OsClient, *c.AuthOpts); err != nil { c.authFailed = err return err } c.authenticated = true } return nil } // DetermineEndpoint is a helper method to determine if the user wants to // override an endpoint returned from the catalog. func (c *Config) DetermineEndpoint(client *gophercloud.ServiceClient, service string) *gophercloud.ServiceClient { finalEndpoint := client.ResourceBaseURL() if v, ok := c.EndpointOverrides[service]; ok { if endpoint, ok := v.(string); ok && endpoint != "" { finalEndpoint = endpoint client.Endpoint = endpoint client.ResourceBase = "" } } log.Printf("[DEBUG] OpenStack Endpoint for %s: %s", service, finalEndpoint) return client } // DetermineRegion is a helper method to determine the region based on // the user's settings. func (c *Config) DetermineRegion(region string) string { // If a resource-level region was not specified, and a provider-level region was set, // use the provider-level region. if region == "" && c.Region != "" { region = c.Region } log.Printf("[DEBUG] OpenStack Region is: %s", region) return region } // The following methods assist with the creation of individual Service Clients // which interact with the various OpenStack services. type commonCommonServiceClientInitFunc func(*gophercloud.ProviderClient, gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) func (c *Config) CommonServiceClientInit(newClient commonCommonServiceClientInitFunc, region, service string) (*gophercloud.ServiceClient, error) { if err := c.Authenticate(); err != nil { return nil, err } client, err := newClient(c.OsClient, gophercloud.EndpointOpts{ Region: c.DetermineRegion(region), Availability: clientconfig.GetEndpointType(c.EndpointType), }) if err != nil { return client, err } // Check if an endpoint override was specified for the volume service. client = c.DetermineEndpoint(client, service) return client, nil } func (c *Config) BlockStorageV1Client(region string) (*gophercloud.ServiceClient, error) { return c.CommonServiceClientInit(openstack.NewBlockStorageV1, region, "volume") } func (c *Config) BlockStorageV2Client(region string) (*gophercloud.ServiceClient, error) { return c.CommonServiceClientInit(openstack.NewBlockStorageV2, region, "volumev2") } func (c *Config) BlockStorageV3Client(region string) (*gophercloud.ServiceClient, error) { return c.CommonServiceClientInit(openstack.NewBlockStorageV3, region, "volumev3") } func (c *Config) ComputeV2Client(region string) (*gophercloud.ServiceClient, error) { return c.CommonServiceClientInit(openstack.NewComputeV2, region, "compute") } func (c *Config) DNSV2Client(region string) (*gophercloud.ServiceClient, error) { return c.CommonServiceClientInit(openstack.NewDNSV2, region, "dns") } func (c *Config) IdentityV3Client(region string) (*gophercloud.ServiceClient, error) { return c.CommonServiceClientInit(openstack.NewIdentityV3, region, "identity") } func (c *Config) ImageV2Client(region string) (*gophercloud.ServiceClient, error) { return c.CommonServiceClientInit(openstack.NewImageServiceV2, region, "image") } func (c *Config) MessagingV2Client(region string) (*gophercloud.ServiceClient, error) { if err := c.Authenticate(); err != nil { return nil, err } client, err := openstack.NewMessagingV2(c.OsClient, "", gophercloud.EndpointOpts{ Region: c.DetermineRegion(region), Availability: clientconfig.GetEndpointType(c.EndpointType), }) if err != nil { return client, err } // Check if an endpoint override was specified for the messaging service. client = c.DetermineEndpoint(client, "message") return client, nil } func (c *Config) NetworkingV2Client(region string) (*gophercloud.ServiceClient, error) { return c.CommonServiceClientInit(openstack.NewNetworkV2, region, "network") } func (c *Config) ObjectStorageV1Client(region string) (*gophercloud.ServiceClient, error) { var client *gophercloud.ServiceClient var err error // If Swift Authentication is being used, return a swauth client. // Otherwise, use a Keystone-based client. if c.Swauth { if !c.DelayedAuth { client, err = swauth.NewObjectStorageV1(c.OsClient, swauth.AuthOpts{ User: c.Username, Key: c.Password, }) if err != nil { return nil, err } } else { c.MutexKV.Lock("SwAuth") defer c.MutexKV.Unlock("SwAuth") if c.swAuthFailed != nil { return nil, c.swAuthFailed } if c.swClient == nil { c.swClient, err = swauth.NewObjectStorageV1(c.OsClient, swauth.AuthOpts{ User: c.Username, Key: c.Password, }) if err != nil { c.swAuthFailed = err return nil, err } } client = c.swClient } } else { if err := c.Authenticate(); err != nil { return nil, err } client, err = openstack.NewObjectStorageV1(c.OsClient, gophercloud.EndpointOpts{ Region: c.DetermineRegion(region), Availability: clientconfig.GetEndpointType(c.EndpointType), }) if err != nil { return client, err } } // Check if an endpoint override was specified for the object-store service. client = c.DetermineEndpoint(client, "object-store") return client, nil } func (c *Config) OrchestrationV1Client(region string) (*gophercloud.ServiceClient, error) { return c.CommonServiceClientInit(openstack.NewOrchestrationV1, region, "orchestration") } func (c *Config) LoadBalancerV2Client(region string) (*gophercloud.ServiceClient, error) { return c.CommonServiceClientInit(openstack.NewLoadBalancerV2, region, "octavia") } func (c *Config) DatabaseV1Client(region string) (*gophercloud.ServiceClient, error) { return c.CommonServiceClientInit(openstack.NewDBV1, region, "database") } func (c *Config) ContainerInfraV1Client(region string) (*gophercloud.ServiceClient, error) { return c.CommonServiceClientInit(openstack.NewContainerInfraV1, region, "container-infra") } func (c *Config) SharedfilesystemV2Client(region string) (*gophercloud.ServiceClient, error) { return c.CommonServiceClientInit(openstack.NewSharedFileSystemV2, region, "sharev2") } func (c *Config) KeyManagerV1Client(region string) (*gophercloud.ServiceClient, error) { return c.CommonServiceClientInit(openstack.NewKeyManagerV1, region, "key-manager") } // A wrapper to determine if logging in gophercloud should be enabled, with a fallback // to the OS_DEBUG environment variable when no explicit configuration is passed. func enableLogging(enable bool) bool { if enable { return true } // if OS_DEBUG is set, log the requests and responses if os.Getenv("OS_DEBUG") != "" { return true } return false } golang-github-gophercloud-utils-0.0~git20231010.80377ec/terraform/auth/doc.go000066400000000000000000000006031467426574700263440ustar00rootroot00000000000000/* Package auth provides common functionality for Terraform and Terraform plugins to authenticate with OpenStack. It includes a wide array of features, such as flexible authentication, debugging, client creation, endpoint customization and more. While this package is specific to Terraform, it can be used for any kind of Terraform feature: core, backend, and plugins. */ package auth golang-github-gophercloud-utils-0.0~git20231010.80377ec/terraform/auth/util.go000066400000000000000000000011631467426574700265560ustar00rootroot00000000000000package auth import ( "fmt" "log" "os" "strings" ) // This is copied directly from Terraform in order to remove a single legacy // vendor dependency. const uaEnvVar = "TF_APPEND_USER_AGENT" func terraformUserAgent(version, sdkVersion string) string { ua := fmt.Sprintf("HashiCorp Terraform/%s (+https://www.terraform.io)", version) if sdkVersion != "" { ua += " " + fmt.Sprintf("Terraform Plugin SDK/%s", sdkVersion) } if add := os.Getenv(uaEnvVar); add != "" { add = strings.TrimSpace(add) if len(add) > 0 { ua += " " + add log.Printf("[DEBUG] Using modified User-Agent: %s", ua) } } return ua } golang-github-gophercloud-utils-0.0~git20231010.80377ec/terraform/hashcode/000077500000000000000000000000001467426574700260665ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/terraform/hashcode/hashcode.go000066400000000000000000000012251467426574700301730ustar00rootroot00000000000000package hashcode import ( "bytes" "fmt" "hash/crc32" ) // String hashes a string to a unique hashcode. // // crc32 returns a uint32, but for our use we need // and non negative integer. Here we cast to an integer // and invert it if the result is negative. func String(s string) int { v := int(crc32.ChecksumIEEE([]byte(s))) if v >= 0 { return v } if -v >= 0 { return -v } // v == MinInt return 0 } // Strings hashes a list of strings to a unique hashcode. func Strings(strings []string) string { var buf bytes.Buffer for _, s := range strings { buf.WriteString(fmt.Sprintf("%s-", s)) } return fmt.Sprintf("%d", String(buf.String())) } golang-github-gophercloud-utils-0.0~git20231010.80377ec/terraform/hashcode/hashcode_test.go000066400000000000000000000014161467426574700312340ustar00rootroot00000000000000package hashcode import ( "testing" ) func TestString(t *testing.T) { v := "hello, world" expected := String(v) for i := 0; i < 100; i++ { actual := String(v) if actual != expected { t.Fatalf("bad: %#v\n\t%#v", actual, expected) } } } func TestStrings(t *testing.T) { v := []string{"hello", ",", "world"} expected := Strings(v) for i := 0; i < 100; i++ { actual := Strings(v) if actual != expected { t.Fatalf("bad: %#v\n\t%#v", actual, expected) } } } func TestString_positiveIndex(t *testing.T) { // "2338615298" hashes to uint32(2147483648) which is math.MinInt32 ips := []string{"192.168.1.3", "192.168.1.5", "2338615298"} for _, ip := range ips { if index := String(ip); index < 0 { t.Fatalf("Bad Index %#v for ip %s", index, ip) } } } golang-github-gophercloud-utils-0.0~git20231010.80377ec/terraform/mutexkv/000077500000000000000000000000001467426574700260135ustar00rootroot00000000000000golang-github-gophercloud-utils-0.0~git20231010.80377ec/terraform/mutexkv/mutexkv.go000066400000000000000000000024471467426574700300540ustar00rootroot00000000000000package mutexkv import ( "log" "sync" ) // MutexKV is a simple key/value store for arbitrary mutexes. It can be used to // serialize changes across arbitrary collaborators that share knowledge of the // keys they must serialize on. // // The initial use case is to let aws_security_group_rule resources serialize // their access to individual security groups based on SG ID. type MutexKV struct { lock sync.Mutex store map[string]*sync.Mutex } // Locks the mutex for the given key. Caller is responsible for calling Unlock // for the same key. func (m *MutexKV) Lock(key string) { log.Printf("[DEBUG] Locking %q", key) m.get(key).Lock() log.Printf("[DEBUG] Locked %q", key) } // Unlock the mutex for the given key. Caller must have called Lock for the same key first. func (m *MutexKV) Unlock(key string) { log.Printf("[DEBUG] Unlocking %q", key) m.get(key).Unlock() log.Printf("[DEBUG] Unlocked %q", key) } // Returns a mutex for the given key, no guarantee of its lock status. func (m *MutexKV) get(key string) *sync.Mutex { m.lock.Lock() defer m.lock.Unlock() mutex, ok := m.store[key] if !ok { mutex = &sync.Mutex{} m.store[key] = mutex } return mutex } // Returns a properly initialized MutexKV. func NewMutexKV() *MutexKV { return &MutexKV{ store: make(map[string]*sync.Mutex), } } golang-github-gophercloud-utils-0.0~git20231010.80377ec/terraform/mutexkv/mutexkv_test.go000066400000000000000000000022071467426574700311050ustar00rootroot00000000000000package mutexkv import ( "testing" "time" ) func TestMutexKVLock(t *testing.T) { s := struct { mkv *MutexKV }{ mkv: NewMutexKV(), } s.mkv.Lock("foo") doneCh := make(chan struct{}) go func() { s.mkv.Lock("foo") close(doneCh) }() select { case <-doneCh: t.Fatal("Second lock was able to be taken. This shouldn't happen.") case <-time.After(50 * time.Millisecond): // pass } } func TestMutexKVUnlock(t *testing.T) { s := struct { mkv *MutexKV }{ mkv: NewMutexKV(), } s.mkv.Lock("foo") s.mkv.Unlock("foo") doneCh := make(chan struct{}) go func() { s.mkv.Lock("foo") close(doneCh) }() select { case <-doneCh: // pass case <-time.After(50 * time.Millisecond): t.Fatal("Second lock blocked after unlock. This shouldn't happen.") } } func TestMutexKVDifferentKeys(t *testing.T) { s := struct { mkv *MutexKV }{ mkv: NewMutexKV(), } s.mkv.Lock("foo") doneCh := make(chan struct{}) go func() { s.mkv.Lock("bar") close(doneCh) }() select { case <-doneCh: // pass case <-time.After(50 * time.Millisecond): t.Fatal("Second lock on a different key blocked. This shouldn't happen.") } }