Skip to content

Commit 6021d9b

Browse files
committed
feat: add resources pkg to improve module development experience
1 parent 06192ea commit 6021d9b

File tree

7 files changed

+283
-0
lines changed

7 files changed

+283
-0
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
github.com/bytedance/mockey v1.2.10
77
github.com/hashicorp/go-hclog v1.6.3
88
github.com/hashicorp/go-plugin v1.6.1
9+
github.com/hashicorp/terraform-svchost v0.1.1
910
github.com/pkg/errors v0.9.1
1011
github.com/prometheus/client_golang v1.20.5
1112
github.com/shirou/gopsutil/v4 v4.24.11

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB1
3838
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
3939
github.com/hashicorp/go-plugin v1.6.1 h1:P7MR2UP6gNKGPp+y7EZw2kOiq4IR9WiqLvp0XOsVdwI=
4040
github.com/hashicorp/go-plugin v1.6.1/go.mod h1:XPHFku2tFo3o3QKFgSYo+cghcUhw1NA1hZyMK0PWAw0=
41+
github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ=
42+
github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc=
4143
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
4244
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
4345
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=

pkg/module/util.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"k8s.io/apimachinery/pkg/runtime"
1111

1212
v1 "kusionstack.io/kusion-api-go/api.kusion.io/v1"
13+
1314
"kusionstack.io/kusion-module-framework/pkg/log"
1415
)
1516

pkg/resources/id.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2024 KusionStack Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package resources
16+
17+
// SegmentSeparator is the separator between segments in Kusion resource ID.
18+
const SegmentSeparator = ":"
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright 2024 KusionStack Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package kubernetes
16+
17+
import (
18+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19+
"k8s.io/apimachinery/pkg/runtime"
20+
"k8s.io/apimachinery/pkg/runtime/schema"
21+
v1 "kusionstack.io/kusion-api-go/api.kusion.io/v1"
22+
23+
"kusionstack.io/kusion-module-framework/pkg/resources"
24+
)
25+
26+
// ToKusionResourceID returns the Kusion resource ID for the given Kubernetes object specified by
27+
// its GroupVersionKind and ObjectMeta.
28+
func ToKusionResourceID(gvk schema.GroupVersionKind, objectMeta metav1.ObjectMeta) string {
29+
// resource id example: apps/v1:Deployment:nginx:nginx-deployment
30+
if gvk.Group == "" {
31+
gvk.Group = "core"
32+
}
33+
34+
id := gvk.Group + resources.SegmentSeparator + gvk.Version + ":" + gvk.Kind
35+
if objectMeta.Namespace != "" {
36+
id += objectMeta.Namespace + ":"
37+
}
38+
id += objectMeta.Name
39+
return id
40+
}
41+
42+
// NewKusionResource creates a Kusion Resource object with the given obj and objectMeta.
43+
func NewKusionResource(obj runtime.Object, objectMeta metav1.ObjectMeta) (*v1.Resource, error) {
44+
// TODO: this function converts int to int64 by default
45+
unstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
46+
if err != nil {
47+
return nil, err
48+
}
49+
50+
gvk := obj.GetObjectKind().GroupVersionKind()
51+
return &v1.Resource{
52+
ID: ToKusionResourceID(gvk, objectMeta),
53+
Type: v1.Kubernetes,
54+
Attributes: unstructured,
55+
DependsOn: nil,
56+
Extensions: map[string]any{
57+
v1.ResourceExtensionGVK: gvk.String(),
58+
},
59+
}, nil
60+
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// Copyright 2024 KusionStack Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package terraform
16+
17+
import (
18+
"errors"
19+
"fmt"
20+
"strings"
21+
22+
svchost "github.com/hashicorp/terraform-svchost"
23+
v1 "kusionstack.io/kusion-api-go/api.kusion.io/v1"
24+
25+
"kusionstack.io/kusion-module-framework/pkg/resources"
26+
)
27+
28+
// DefaultProviderRegistryHost is the hostname used for provider addresses that do
29+
// not have an explicit hostname.
30+
const DefaultProviderRegistryHost = "registry.terraform.io"
31+
32+
var (
33+
// errInvalidSource means provider's source is invalid, which must be in [<HOSTNAME>/]<NAMESPACE>/<TYPE> format
34+
errInvalidSource = errors.New(`invalid provider source string, must be in the format "[hostname/]namespace/name"`)
35+
// errInvalidVersion means provider's version constraint is invalid, which must not be empty.
36+
errInvalidVersion = errors.New("invalid provider version constraint")
37+
// errInvalidResourceTypeOrName resourceType or resourceName is invalid, which must not be empty.
38+
errInvalidResourceTypeOrName = errors.New("resourceType or resourceName is empty")
39+
)
40+
41+
// NewProvider constructs a provider instance from given source, version and configuration arguments.
42+
func NewProvider(providerConfigs map[string]any, source, version string) (Provider, error) {
43+
var ret Provider
44+
45+
if version == "" {
46+
return ret, errInvalidVersion
47+
}
48+
49+
_, err := parseProviderSourceString(source)
50+
if err != nil {
51+
return ret, err
52+
}
53+
54+
ret.Source = source
55+
ret.Version = version
56+
ret.ProviderConfigs = providerConfigs
57+
return ret, nil
58+
}
59+
60+
// ToKusionResourceID takes provider, resource info and returns string representing Kusion qualified resource ID.
61+
func ToKusionResourceID(p Provider, resourceType, resourceName string) (string, error) {
62+
if p.Version == "" {
63+
return "", errInvalidVersion
64+
}
65+
if resourceType == "" || resourceName == "" {
66+
return "", errInvalidResourceTypeOrName
67+
}
68+
tfProvider, err := parseProviderSourceString(p.Source)
69+
if err != nil {
70+
return "", err
71+
}
72+
73+
tfProviderStr := tfProvider.String()
74+
return strings.Join([]string{tfProviderStr, resourceType, resourceName}, resources.SegmentSeparator), nil
75+
}
76+
77+
// NewKusionResource creates a Kusion Resource object with the given resourceType, resourceID, attributes.
78+
func NewKusionResource(p Provider, resourceType, resourceID string,
79+
attrs map[string]interface{}, dependsOn []string,
80+
) (*v1.Resource, error) {
81+
if resourceType == "" {
82+
return nil, errInvalidResourceTypeOrName
83+
}
84+
tfProvider, err := parseProviderSourceString(p.Source)
85+
if err != nil {
86+
return nil, err
87+
}
88+
tfProvider.Version = p.Version
89+
90+
// put provider info into extensions
91+
extensions := make(map[string]interface{}, 3)
92+
extensions["provider"] = tfProvider.String()
93+
extensions["providerMeta"] = p.ProviderConfigs
94+
extensions["resourceType"] = resourceType
95+
96+
return &v1.Resource{
97+
ID: resourceID,
98+
Type: v1.Terraform,
99+
Attributes: attrs,
100+
DependsOn: dependsOn,
101+
Extensions: extensions,
102+
}, nil
103+
}
104+
105+
// parseProviderSourceString parses the source attribute and returns a terraform provider.
106+
//
107+
// The following are valid source string formats:
108+
//
109+
// name
110+
// namespace/name
111+
// hostname/namespace/name
112+
func parseProviderSourceString(str string) (TFProvider, error) {
113+
var ret TFProvider
114+
115+
// split the source string into individual components
116+
parts := strings.Split(str, "/")
117+
if len(parts) == 0 || len(parts) > 3 {
118+
return ret, errInvalidSource
119+
}
120+
121+
// check for an invalid empty string in any part
122+
for i := range parts {
123+
if parts[i] == "" {
124+
return ret, errInvalidSource
125+
}
126+
}
127+
128+
// check the 'name' portion, which is always the last part
129+
givenName := parts[len(parts)-1]
130+
ret.Type = givenName
131+
ret.Hostname = DefaultProviderRegistryHost
132+
133+
if len(parts) == 1 {
134+
ret.Namespace = "hashicorp"
135+
return ret, nil
136+
}
137+
138+
if len(parts) >= 2 {
139+
// the namespace is always the second-to-last part
140+
givenNamespace := parts[len(parts)-2]
141+
ret.Namespace = givenNamespace
142+
}
143+
144+
// Final Case: 3 parts
145+
if len(parts) == 3 {
146+
// the namespace is always the first part in a three-part source string
147+
hn, err := svchost.ForComparison(parts[0])
148+
if err != nil {
149+
return ret, fmt.Errorf("invalid provider source hostname %q in source %q: %s", hn, str, err)
150+
}
151+
ret.Hostname = hn
152+
}
153+
154+
return ret, nil
155+
}

pkg/resources/terraform/types.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2024 KusionStack Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package terraform
16+
17+
import (
18+
svchost "github.com/hashicorp/terraform-svchost"
19+
20+
"kusionstack.io/kusion-module-framework/pkg/resources"
21+
)
22+
23+
// Provider contains all the information of a specified Kusion provider, which not only includes the
24+
// source address, version constraint of required provider, but also various configuration arguments used
25+
// to configure required provider before Terraform can use them.
26+
type Provider struct {
27+
// Source address of the provider.
28+
Source string `yaml:"source" json:"source"`
29+
// Version constraint of the provider.
30+
Version string `yaml:"version" json:"version"`
31+
// Configuration arguments of the provider.
32+
ProviderConfigs map[string]any `yaml:"providerConfigs" json:"providerConfigs"`
33+
}
34+
35+
// TFProvider encapsulates a single terraform provider type.
36+
type TFProvider struct {
37+
Type string
38+
Namespace string
39+
Hostname svchost.Hostname
40+
Version string
41+
}
42+
43+
// String returns an FQN string, intended for use in machine-readable output.
44+
func (tp TFProvider) String() string {
45+
return tp.Hostname.ForDisplay() + resources.SegmentSeparator + tp.Namespace + resources.SegmentSeparator + tp.Type + resources.SegmentSeparator + tp.Version
46+
}

0 commit comments

Comments
 (0)