Skip to content

Commit d81c9ad

Browse files
author
Joshua Reed
committed
Create and delete domains works for multi-level new domains now.
1 parent 4f57387 commit d81c9ad

File tree

5 files changed

+256
-3
lines changed

5 files changed

+256
-3
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module github.com/aws/cluster-api-provider-cloudstack
33
go 1.16
44

55
require (
6-
github.com/apache/cloudstack-go/v2 v2.13.0
6+
github.com/apache/cloudstack-go/v2 v2.13.1
77
github.com/go-logr/logr v0.4.0
88
github.com/golang/mock v1.6.0
99
github.com/hashicorp/go-multierror v1.1.1

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
7676
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
7777
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
7878
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
79-
github.com/apache/cloudstack-go/v2 v2.13.0 h1:t0uj7QxQpnzD/LSTP6a4w2NTuZXisxIM/mIDNkF44lc=
80-
github.com/apache/cloudstack-go/v2 v2.13.0/go.mod h1:aosD8Svfu5nhH5Sp4zcsVV1hT5UGt3mTgRXM8YqTKe0=
79+
github.com/apache/cloudstack-go/v2 v2.13.1 h1:UHhNJ+5coUsgk9D5WBbqbY8hYfJ1bXgNxaSg2uGz4Ns=
80+
github.com/apache/cloudstack-go/v2 v2.13.1/go.mod h1:aosD8Svfu5nhH5Sp4zcsVV1hT5UGt3mTgRXM8YqTKe0=
8181
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
8282
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
8383
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=

test/helpers/suite_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package helpers_test
2+
3+
import (
4+
"testing"
5+
6+
. "github.com/onsi/ginkgo"
7+
. "github.com/onsi/gomega"
8+
)
9+
10+
func TestCloud(t *testing.T) {
11+
RegisterFailHandler(Fail)
12+
RunSpecs(t, "Cloud Suite")
13+
}

test/helpers/user.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package helpers
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/apache/cloudstack-go/v2/cloudstack"
8+
"github.com/aws/cluster-api-provider-cloudstack/pkg/cloud"
9+
)
10+
11+
// GetDomainByPath fetches a domain by its path.
12+
func GetDomainByPath(csClient *cloudstack.CloudStackClient, path string) (string, error, bool) {
13+
// Split path and get name.
14+
path = strings.Trim(path, "/")
15+
tokens := []string{}
16+
tokens = strings.Split(path, "/")
17+
18+
// Ensure the path begins with ROOT.
19+
if !strings.EqualFold(tokens[0], "ROOT") {
20+
tokens = append([]string{"ROOT"}, tokens...)
21+
} else {
22+
tokens[0] = "ROOT"
23+
}
24+
path = strings.Join(tokens, "/")
25+
26+
// Set present search/list parameters.
27+
p := csClient.Domain.NewListDomainsParams()
28+
p.SetListall(true)
29+
30+
// If path was provided also use level narrow the search for domain.
31+
if level := len(tokens) - 1; level >= 0 {
32+
p.SetLevel(level)
33+
}
34+
35+
if resp, err := csClient.Domain.ListDomains(p); err != nil {
36+
return "", err, false
37+
} else {
38+
for _, domain := range resp.Domains {
39+
if domain.Path == path {
40+
return domain.Id, nil, true
41+
}
42+
}
43+
}
44+
45+
return "", nil, false
46+
}
47+
48+
// CreateDomainUnderParent creates a domain as a sub-domain of the passed parent.
49+
func CreateDomainUnderParent(csClient *cloudstack.CloudStackClient, parentID string, domainName string) (string, error) {
50+
p := csClient.Domain.NewCreateDomainParams(domainName)
51+
p.SetParentdomainid(parentID)
52+
resp, err := csClient.Domain.CreateDomain(p)
53+
if err != nil {
54+
return "", err
55+
}
56+
return resp.Id, nil
57+
}
58+
59+
// GetOrCreateDomain gets or creates a domain as specified in the passed domain object.
60+
func GetOrCreateDomain(domain *cloud.Domain, csClient *cloudstack.CloudStackClient) error {
61+
// Split the specified domain path and prepend ROOT/ if it's missing.
62+
domain.Path = strings.Trim(domain.Path, "/")
63+
tokens := strings.Split(domain.Path, "/")
64+
if strings.EqualFold(tokens[0], "root") {
65+
tokens[0] = "ROOT"
66+
} else {
67+
tokens = append([]string{"ROOT"}, tokens...)
68+
}
69+
domain.Path = strings.Join(tokens, "/")
70+
71+
// Fetch ROOT domain ID.
72+
rootID, err, _ := GetDomainByPath(csClient, "ROOT")
73+
if err != nil {
74+
return err
75+
}
76+
77+
// Iteratively create the domain from its path.
78+
parentID := rootID
79+
currPath := "ROOT"
80+
for _, nextDomainName := range tokens[1:] {
81+
currPath = currPath + "/" + nextDomainName
82+
if nextId, err, found := GetDomainByPath(csClient, currPath); err != nil {
83+
return err
84+
} else if !found {
85+
if nextId, err := CreateDomainUnderParent(csClient, parentID, nextDomainName); err != nil {
86+
return err
87+
} else {
88+
parentID = nextId
89+
}
90+
} else {
91+
parentID = nextId
92+
}
93+
}
94+
domain.ID = parentID
95+
domain.Name = tokens[len(tokens)-1]
96+
domain.Path = strings.Join(tokens, "/")
97+
return nil
98+
}
99+
100+
// DeleteDomain deletes a domain by ID.
101+
func DeleteDomain(csClient *cloudstack.CloudStackClient, domainID string) error {
102+
p := csClient.Domain.NewDeleteDomainParams(domainID)
103+
p.SetCleanup(true)
104+
resp, err := csClient.Domain.DeleteDomain(p)
105+
if !resp.Success {
106+
return fmt.Errorf("unsuccessful deletion of domain with ID %s", domainID)
107+
}
108+
return err
109+
}
110+
111+
// // CreateAccount creates a domain as specified in the passed account object.
112+
// func CreateAccount(account *cloud.Account, csClient *cloudstack.CloudStackClient) error {
113+
// return nil
114+
// }
115+
116+
// // CreateUser creates a domain as specified in the passed account object.
117+
// func CreateUser(user *cloud.User, csClient *cloudstack.CloudStackClient) error {
118+
// return nil
119+
// }

test/helpers/user_test.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
Copyright 2022 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package helpers_test
18+
19+
import (
20+
"github.com/apache/cloudstack-go/v2/cloudstack"
21+
"github.com/aws/cluster-api-provider-cloudstack/pkg/cloud"
22+
"github.com/aws/cluster-api-provider-cloudstack/test/helpers"
23+
. "github.com/onsi/ginkgo"
24+
. "github.com/onsi/gomega"
25+
"github.com/pkg/errors"
26+
"gopkg.in/ini.v1"
27+
)
28+
29+
var _ = Describe("Test helper methods", func() {
30+
31+
Context("Domain Creation and Deletion Integ Tests", func() {
32+
var csClient *cloudstack.CloudStackClient
33+
ccPath := "../../cloud-config"
34+
var connectionErr error
35+
conf := cloud.Config{}
36+
if rawCfg, err := ini.Load("../../cloud-config"); err != nil {
37+
connectionErr = errors.Wrapf(err, "reading config at path %s:", ccPath)
38+
} else if g := rawCfg.Section("Global"); len(g.Keys()) == 0 {
39+
connectionErr = errors.New("section Global not found")
40+
} else if err = rawCfg.Section("Global").StrictMapTo(&conf); err != nil {
41+
connectionErr = errors.Wrapf(err, "parsing [Global] section from config at path %s:", ccPath)
42+
}
43+
44+
csClient = cloudstack.NewAsyncClient(conf.APIURL, conf.APIKey, conf.SecretKey, conf.VerifySSL)
45+
46+
// Get the root domain's ID.
47+
rootDomainID, err, found := helpers.GetDomainByPath(csClient, "ROOT/")
48+
Ω(err).ShouldNot(HaveOccurred())
49+
Ω(rootDomainID).ShouldNot(BeEmpty())
50+
Ω(found).Should(BeTrue())
51+
52+
BeforeEach(func() {
53+
if connectionErr != nil { // Only do these tests if an actual ACS instance is available via cloud-config.
54+
Skip("Could not connect to ACS instance.")
55+
}
56+
})
57+
58+
AfterEach(func() {
59+
for _, path := range []string{"ROOT/someNewDomain", "ROOT/blah"} {
60+
// Delete any created domains.
61+
id, err, found := helpers.GetDomainByPath(csClient, path)
62+
Ω(err).ShouldNot(HaveOccurred())
63+
if found {
64+
Ω(helpers.DeleteDomain(csClient, id)).Should(Succeed())
65+
}
66+
}
67+
})
68+
69+
It("Can get the ROOT domain's ID.", func() {
70+
id, err, found := helpers.GetDomainByPath(csClient, "ROOT/")
71+
Ω(err).ShouldNot(HaveOccurred())
72+
Ω(id).ShouldNot(BeEmpty())
73+
Ω(found).Should(BeTrue())
74+
})
75+
76+
It("Doesn't error when unable to get a domain's ID.", func() {
77+
id, err, found := helpers.GetDomainByPath(csClient, "ROOT/blahnotpresent")
78+
Ω(err).ShouldNot(HaveOccurred())
79+
Ω(found).Should(BeFalse())
80+
Ω(id).Should(BeEmpty())
81+
})
82+
83+
It("Can create a domain under a parent domain.", func() {
84+
id, err := helpers.CreateDomainUnderParent(csClient, rootDomainID, "someNewDomain")
85+
Ω(id).ShouldNot(BeEmpty())
86+
Ω(err).ShouldNot(HaveOccurred())
87+
})
88+
89+
It("Returns an appropriate error when the domain already exists.", func() {
90+
someDomain := &cloud.Domain{Name: "blah", Path: "blah"}
91+
Ω(helpers.GetOrCreateDomain(someDomain, csClient)).Should(Succeed())
92+
Ω(someDomain.Name).Should(Equal("blah"))
93+
Ω(someDomain.Path).Should(Equal("ROOT/blah"))
94+
Ω(someDomain.ID).ShouldNot(BeEmpty())
95+
_, err = helpers.CreateDomainUnderParent(csClient, rootDomainID, "blah")
96+
Ω(err).Should(HaveOccurred())
97+
Ω(err.Error()).Should(ContainSubstring("already exists"))
98+
})
99+
100+
It("Doesn't error if the domain already exists.", func() {
101+
someDomain := &cloud.Domain{Name: "blah", Path: "blah"}
102+
Ω(helpers.GetOrCreateDomain(someDomain, csClient)).Should(Succeed())
103+
Ω(someDomain.Name).Should(Equal("blah"))
104+
Ω(someDomain.Path).Should(Equal("ROOT/blah"))
105+
Ω(someDomain.ID).ShouldNot(BeEmpty())
106+
107+
Ω(helpers.GetOrCreateDomain(someDomain, csClient)).Should(Succeed())
108+
Ω(someDomain.Name).Should(Equal("blah"))
109+
Ω(someDomain.Path).Should(Equal("ROOT/blah"))
110+
Ω(someDomain.ID).ShouldNot(BeEmpty())
111+
})
112+
113+
It("Can create a wholly new multi-level sub-domain path.", func() {
114+
someDomain := &cloud.Domain{Name: "tooBlah", Path: "ROOT/someNewDomain/tooBlah"}
115+
Ω(helpers.GetOrCreateDomain(someDomain, csClient)).Should(Succeed())
116+
Ω(someDomain.Name).Should(Equal("tooBlah"))
117+
Ω(someDomain.Path).Should(Equal("ROOT/someNewDomain/tooBlah"))
118+
Ω(someDomain.ID).ShouldNot(BeEmpty())
119+
})
120+
})
121+
})

0 commit comments

Comments
 (0)