Skip to content

Commit

Permalink
feat: add resources pkg to improve module development experience
Browse files Browse the repository at this point in the history
  • Loading branch information
adohe committed Dec 11, 2024
1 parent 06192ea commit 8de107c
Show file tree
Hide file tree
Showing 7 changed files with 282 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/bytedance/mockey v1.2.10
github.com/hashicorp/go-hclog v1.6.3
github.com/hashicorp/go-plugin v1.6.1
github.com/hashicorp/terraform-svchost v0.1.1
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.20.5
github.com/shirou/gopsutil/v4 v4.24.11
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB1
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-plugin v1.6.1 h1:P7MR2UP6gNKGPp+y7EZw2kOiq4IR9WiqLvp0XOsVdwI=
github.com/hashicorp/go-plugin v1.6.1/go.mod h1:XPHFku2tFo3o3QKFgSYo+cghcUhw1NA1hZyMK0PWAw0=
github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ=
github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc=
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
Expand Down
1 change: 1 addition & 0 deletions pkg/module/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"

v1 "kusionstack.io/kusion-api-go/api.kusion.io/v1"

"kusionstack.io/kusion-module-framework/pkg/log"
)

Expand Down
18 changes: 18 additions & 0 deletions pkg/resources/id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright 2024 KusionStack Authors
//
// 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.

package resources

// SegmentSeparator is the separator between segments in Kusion resource ID.
const SegmentSeparator = ":"
60 changes: 60 additions & 0 deletions pkg/resources/kubernetes/kubernetes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright 2024 KusionStack Authors
//
// 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.

package kubernetes

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
v1 "kusionstack.io/kusion-api-go/api.kusion.io/v1"

"kusionstack.io/kusion-module-framework/pkg/resources"
)

// ToKusionResourceID returns the Kusion resource ID for the given Kubernetes object specified by
// its GroupVersionKind and ObjectMeta.
func ToKusionResourceID(gvk schema.GroupVersionKind, objectMeta metav1.ObjectMeta) string {
// resource id example: apps/v1:Deployment:nginx:nginx-deployment
if gvk.Group == "" {
gvk.Group = "core"
}

id := gvk.Group + resources.SegmentSeparator + gvk.Version + resources.SegmentSeparator + gvk.Kind
if objectMeta.Namespace != "" {
id += resources.SegmentSeparator + objectMeta.Namespace
}
id += resources.SegmentSeparator + objectMeta.Name
return id
}

// NewKusionResource creates a Kusion Resource object with the given obj and objectMeta.
func NewKusionResource(obj runtime.Object, objectMeta metav1.ObjectMeta) (*v1.Resource, error) {
// TODO: this function converts int to int64 by default
unstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
if err != nil {
return nil, err
}

gvk := obj.GetObjectKind().GroupVersionKind()
return &v1.Resource{
ID: ToKusionResourceID(gvk, objectMeta),
Type: v1.Kubernetes,
Attributes: unstructured,
DependsOn: nil,
Extensions: map[string]any{
v1.ResourceExtensionGVK: gvk.String(),
},
}, nil
}
150 changes: 150 additions & 0 deletions pkg/resources/terraform/terraform.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// Copyright 2024 KusionStack Authors
//
// 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.

package terraform

import (
"errors"
"fmt"
"strings"

svchost "github.com/hashicorp/terraform-svchost"
v1 "kusionstack.io/kusion-api-go/api.kusion.io/v1"

"kusionstack.io/kusion-module-framework/pkg/resources"
)

// DefaultProviderRegistryHost is the hostname used for provider addresses that do
// not have an explicit hostname.
const DefaultProviderRegistryHost = "registry.terraform.io"

var (
// errInvalidSource means provider's source is invalid, which must be in [<HOSTNAME>/]<NAMESPACE>/<TYPE> format
errInvalidSource = errors.New(`invalid provider source string, must be in the format "[hostname/]namespace/name"`)
// errInvalidVersion means provider's version constraint is invalid, which must not be empty.
errInvalidVersion = errors.New("invalid provider version constraint")
// errInvalidResourceTypeOrName resourceType or resourceName is invalid, which must not be empty.
errInvalidResourceTypeOrName = errors.New("resourceType or resourceName is empty")
)

// NewProvider constructs a provider instance from given source, version and configuration arguments.
func NewProvider(providerConfigs map[string]any, source, version string) (Provider, error) {
var ret Provider

if version == "" {
return ret, errInvalidVersion
}

_, err := parseProviderSourceString(source)
if err != nil {
return ret, err
}

ret.Source = source
ret.Version = version
ret.ProviderConfigs = providerConfigs
return ret, nil
}

// ToKusionResourceID takes provider, resource info and returns string representing Kusion qualified resource ID.
func ToKusionResourceID(p Provider, resourceType, resourceName string) (string, error) {
if p.Version == "" {
return "", errInvalidVersion
}
if resourceType == "" || resourceName == "" {
return "", errInvalidResourceTypeOrName
}
tfProvider, err := parseProviderSourceString(p.Source)
if err != nil {
return "", err
}

tfProviderStr := tfProvider.String()
return strings.Join([]string{tfProviderStr, resourceType, resourceName}, resources.SegmentSeparator), nil
}

// NewKusionResource creates a Kusion Resource object with the given resourceType, resourceID, attributes.
func NewKusionResource(p Provider, resourceType, resourceID string,
attrs map[string]interface{}, dependsOn []string,
) (*v1.Resource, error) {
if resourceType == "" {
return nil, errInvalidResourceTypeOrName
}

// put provider info into extensions
extensions := make(map[string]interface{}, 3)
extensions["provider"] = p.String()
extensions["providerMeta"] = p.ProviderConfigs
extensions["resourceType"] = resourceType

return &v1.Resource{
ID: resourceID,
Type: v1.Terraform,
Attributes: attrs,
DependsOn: dependsOn,
Extensions: extensions,
}, nil
}

// parseProviderSourceString parses the source attribute and returns a terraform provider.
//
// The following are valid source string formats:
//
// name
// namespace/name
// hostname/namespace/name
func parseProviderSourceString(str string) (TFProvider, error) {
var ret TFProvider

// split the source string into individual components
parts := strings.Split(str, "/")
if len(parts) == 0 || len(parts) > 3 {
return ret, errInvalidSource
}

// check for an invalid empty string in any part
for i := range parts {
if parts[i] == "" {
return ret, errInvalidSource
}
}

// check the 'name' portion, which is always the last part
givenName := parts[len(parts)-1]
ret.Type = givenName
ret.Hostname = DefaultProviderRegistryHost

if len(parts) == 1 {
ret.Namespace = "hashicorp"
return ret, nil
}

if len(parts) >= 2 {
// the namespace is always the second-to-last part
givenNamespace := parts[len(parts)-2]
ret.Namespace = givenNamespace
}

// Final Case: 3 parts
if len(parts) == 3 {
// the namespace is always the first part in a three-part source string
hn, err := svchost.ForComparison(parts[0])
if err != nil {
return ret, fmt.Errorf("invalid provider source hostname %q in source %q: %s", hn, str, err)
}
ret.Hostname = hn
}

return ret, nil
}
50 changes: 50 additions & 0 deletions pkg/resources/terraform/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2024 KusionStack Authors
//
// 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.

package terraform

import (
svchost "github.com/hashicorp/terraform-svchost"

"kusionstack.io/kusion-module-framework/pkg/resources"
)

// Provider contains all the information of a specified Kusion provider, which not only includes the
// source address, version constraint of required provider, but also various configuration arguments used
// to configure required provider before Terraform can use them.
type Provider struct {
// Source address of the provider.
Source string `yaml:"source" json:"source"`
// Version constraint of the provider.
Version string `yaml:"version" json:"version"`
// Configuration arguments of the provider.
ProviderConfigs map[string]any `yaml:"providerConfigs" json:"providerConfigs"`
}

// String returns a qualified string, intended for use in resource extension.
func (p Provider) String() string {
return p.Source + "/" + p.Version
}

// TFProvider encapsulates a single terraform provider type.
type TFProvider struct {
Type string
Namespace string
Hostname svchost.Hostname
}

// String returns an FQN string, intended for use in machine-readable output.
func (tp TFProvider) String() string {
return tp.Hostname.ForDisplay() + resources.SegmentSeparator + tp.Namespace + resources.SegmentSeparator + tp.Type
}

0 comments on commit 8de107c

Please sign in to comment.