Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 Restore v1alpha3/v1alpha4 conversion to fix SSA issue #2739

Merged
merged 4 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ run:
go: "1.21"
skip-files:
- "zz_generated.*\\.go$"
- "_conversion\\.go$"
- "vendored_cluster_api\\.go$"
allow-parallel-runners: true

linters:
Expand Down Expand Up @@ -111,6 +113,10 @@ linters-settings:
# CAPV
- pkg: sigs.k8s.io/cluster-api-provider-vsphere/apis/v1beta1
alias: infrav1
- pkg: sigs.k8s.io/cluster-api-provider-vsphere/apis/v1alpha3
alias: infrav1alpha3
- pkg: sigs.k8s.io/cluster-api-provider-vsphere/apis/v1alpha4
alias: infrav1alpha4
- pkg: sigs.k8s.io/cluster-api-provider-vsphere/apis/vmware/v1beta1
alias: vmwarev1
# VMware Operator
Expand Down Expand Up @@ -275,3 +281,25 @@ issues:
- stylecheck
text: "ST1016: methods on the same type should have the same receiver name"
path: ^apis\/.*\/.*conversion.*\.go$
# missing comments on v1alpha3 and v1alpha4 packages. These rules should be removed when those packages are removed.
- linters:
- revive
text: "package-comments"
path: ^(apis/(v1alpha3|v1alpha4)\/.*)\.go$
- linters:
- stylecheck
text: "ST1000"
path: ^(apis/(v1alpha3|v1alpha4)\/.*)\.go$
- linters:
- revive
text: exported (.*) should have comment (.*)or be unexported
path: ^(apis/(v1alpha3|v1alpha4)\/.*)\.go$
# wrong comment
- linters:
- revive
text: comment on exported (.*) should be of the form (.*)
path: ^(apis/(v1alpha3|v1alpha4)\/.*)\.go$
- linters:
- stylecheck
text: ST1021|ST1020
path: ^(apis/(v1alpha3|v1alpha4)\/.*)\.go$
16 changes: 14 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -263,12 +263,14 @@ help: # Display this help

.PHONY: generate
generate: ## Run all generate targets
$(MAKE) generate-modules generate-manifests generate-go-deepcopy generate-flavors
$(MAKE) generate-modules generate-manifests generate-go-deepcopy generate-go-conversions generate-flavors

.PHONY: generate-manifests
generate-manifests: $(CONTROLLER_GEN) ## Generate manifests e.g. CRD, RBAC etc.
$(MAKE) clean-generated-yaml SRC_DIRS="$(CRD_ROOT),$(SUPERVISOR_CRD_ROOT),$(VMOP_CRD_ROOT),./config/webhook/manifests.yaml"
$(CONTROLLER_GEN) \
paths=./apis/v1alpha3 \
paths=./apis/v1alpha4 \
paths=./apis/v1beta1 \
paths=./internal/webhooks \
crd:crdVersions=v1 \
Expand Down Expand Up @@ -309,6 +311,16 @@ generate-go-deepcopy: $(CONTROLLER_GEN) ## Generate deepcopy go code for core
object:headerFile=./hack/boilerplate/boilerplate.generatego.txt \
paths=./$(VCSIM_DIR)/api/...

.PHONY: generate-go-conversions
generate-go-conversions: $(CONTROLLER_GEN) $(CONVERSION_GEN) ## Runs Go related generate targets
$(MAKE) clean-generated-conversions SRC_DIRS="./apis/v1alpha3,./apis/v1alpha4"
$(CONVERSION_GEN) \
--input-dirs=./apis/v1alpha3 \
--input-dirs=./apis/v1alpha4 \
--build-tag=ignore_autogenerated \
--output-file-base=zz_generated.conversion $(CONVERSION_GEN_OUTPUT_BASE) \
--go-header-file=./hack/boilerplate/boilerplate.generatego.txt

.PHONY: generate-modules
generate-modules: ## Run go mod tidy to ensure modules are up to date
go mod tidy
Expand Down Expand Up @@ -387,7 +399,7 @@ APIDIFF_OLD_COMMIT ?= $(shell git rev-parse origin/main)
apidiff: $(GO_APIDIFF) ## Check for API differences
$(GO_APIDIFF) $(APIDIFF_OLD_COMMIT) --print-compatible

ALL_VERIFY_CHECKS = licenses boilerplate shellcheck modules gen doctoc flavors import-restrictions
ALL_VERIFY_CHECKS = licenses boilerplate shellcheck modules gen conversions doctoc flavors import-restrictions

.PHONY: verify
verify: $(addprefix verify-,$(ALL_VERIFY_CHECKS)) ## Run all verify-* targets
Expand Down
268 changes: 268 additions & 0 deletions apis/v1alpha3/cloudprovider_encoding.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
/*
Copyright 2019 The Kubernetes 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 v1alpha3

import (
"bytes"
"fmt"
"io"
"reflect"
"regexp"
"sort"
"strings"

"github.com/pkg/errors"
gcfg "gopkg.in/gcfg.v1"
)

const gcfgTag = "gcfg"

var iniEscapeChars = regexp.MustCompile(`([\\"])`)

// MarshalINI marshals the cloud provider configuration to INI-style
// configuration data.
func (c *CPIConfig) MarshalINI() ([]byte, error) {
if c == nil {
return nil, errors.New("config is nil")
}

Check warning on line 41 in apis/v1alpha3/cloudprovider_encoding.go

View check run for this annotation

Codecov / codecov/patch

apis/v1alpha3/cloudprovider_encoding.go#L40-L41

Added lines #L40 - L41 were not covered by tests

buf := &bytes.Buffer{}

// Get the reflected type and value of the CPIConfig object.
configValue := reflect.ValueOf(*c)
configType := reflect.TypeOf(*c)

for sectionIndex := 0; sectionIndex < configValue.NumField(); sectionIndex++ {
sectionType := configType.Field(sectionIndex)
sectionValue := configValue.Field(sectionIndex)

// Get the value of the gcfg tag to help determine the section
// name and whether to omit an empty value. Also ignore fields without the gcfg tag
sectionName, omitEmpty, hasTag := parseGcfgTag(sectionType)
if !hasTag {
continue
}

// Do not marshal a section if it is empty.
if omitEmpty && isEmpty(sectionValue) {
continue
}

switch sectionValue.Kind() {
case reflect.Map:
keys := sectionValue.MapKeys()
sort.Slice(keys, func(i, j int) bool {
return keys[i].String() < keys[j].String()
})

for _, key := range keys {
sectionNameKey, sectionValue := key, sectionValue.MapIndex(key)
sectionName := fmt.Sprintf(`%s "%v"`, sectionName, sectionNameKey.String())
if err := c.marshalINISectionProperties(buf, sectionValue, sectionName); err != nil {
return nil, err
}

Check warning on line 77 in apis/v1alpha3/cloudprovider_encoding.go

View check run for this annotation

Codecov / codecov/patch

apis/v1alpha3/cloudprovider_encoding.go#L76-L77

Added lines #L76 - L77 were not covered by tests
}
default:
if err := c.marshalINISectionProperties(buf, sectionValue, sectionName); err != nil {
return nil, err
}

Check warning on line 82 in apis/v1alpha3/cloudprovider_encoding.go

View check run for this annotation

Codecov / codecov/patch

apis/v1alpha3/cloudprovider_encoding.go#L81-L82

Added lines #L81 - L82 were not covered by tests
}
}

return buf.Bytes(), nil
}

func (c *CPIConfig) marshalINISectionProperties(out io.Writer, sectionValue reflect.Value, sectionName string) error {
switch sectionValue.Kind() {
case reflect.Interface, reflect.Ptr:
return c.marshalINISectionProperties(out, sectionValue.Elem(), sectionName)

Check warning on line 92 in apis/v1alpha3/cloudprovider_encoding.go

View check run for this annotation

Codecov / codecov/patch

apis/v1alpha3/cloudprovider_encoding.go#L91-L92

Added lines #L91 - L92 were not covered by tests
}

fmt.Fprintf(out, "[%s]\n", sectionName)

sectionType := sectionValue.Type()
for propertyIndex := 0; propertyIndex < sectionType.NumField(); propertyIndex++ {
propertyType := sectionType.Field(propertyIndex)
propertyValue := sectionValue.Field(propertyIndex)

// Get the value of the gcfg tag to help determine the property
// name and whether to omit an empty value.
propertyName, omitEmpty, hasTag := parseGcfgTag(propertyType)
if !hasTag {
continue

Check warning on line 106 in apis/v1alpha3/cloudprovider_encoding.go

View check run for this annotation

Codecov / codecov/patch

apis/v1alpha3/cloudprovider_encoding.go#L106

Added line #L106 was not covered by tests
}

// Do not marshal a property if it is empty.
if omitEmpty && isEmpty(propertyValue) {
continue
}

switch propertyValue.Kind() {
case reflect.Interface, reflect.Ptr:
propertyValue = propertyValue.Elem()

Check warning on line 116 in apis/v1alpha3/cloudprovider_encoding.go

View check run for this annotation

Codecov / codecov/patch

apis/v1alpha3/cloudprovider_encoding.go#L115-L116

Added lines #L115 - L116 were not covered by tests
}

fmt.Fprintf(out, "%s", propertyName)
if propertyValue.IsValid() {
rawVal := fmt.Sprintf("%v", propertyValue.Interface())
val := iniEscapeChars.ReplaceAllString(rawVal, "\\$1")
val = strings.ReplaceAll(val, "\t", "\\t")
if propertyValue.Kind() == reflect.String {
val = "\"" + val + "\""
}
fmt.Fprintf(out, " = %s\n", val)
}
}

fmt.Fprintf(out, "\n")

return nil
}

func parseGcfgTag(field reflect.StructField) (string, bool, bool) {
name := field.Name
omitEmpty := false
hasTag := false

if tagVal, ok := field.Tag.Lookup(gcfgTag); ok {
hasTag = true
tagParts := strings.Split(tagVal, ",")
lenTagParts := len(tagParts)
if lenTagParts > 0 {
tagName := tagParts[0]
if tagName != "" && tagName != "-" {
name = tagName
}
}
if lenTagParts > 1 {
omitEmpty = tagParts[1] == "omitempty"
}
}

return name, omitEmpty, hasTag
}

// UnmarshalINIOptions defines the options used to influence how INI data is
// unmarshalled.
//
// +kubebuilder:object:generate=false
type UnmarshalINIOptions struct {
// WarnAsFatal indicates that warnings that occur when unmarshalling INI
// data should be treated as fatal errors.
WarnAsFatal bool
}

// UnmarshalINIOptionFunc is used to set unmarshal options.
//
// +kubebuilder:object:generate=false
type UnmarshalINIOptionFunc func(*UnmarshalINIOptions)

// WarnAsFatal sets the option to treat warnings as fatal errors when
// unmarshalling INI data.
func WarnAsFatal(opts *UnmarshalINIOptions) {
opts.WarnAsFatal = true
}

// UnmarshalINI unmarshals the cloud provider configuration from INI-style
// configuration data.
func (c *CPIConfig) UnmarshalINI(data []byte, optFuncs ...UnmarshalINIOptionFunc) error {
opts := &UnmarshalINIOptions{}
for _, setOpts := range optFuncs {
setOpts(opts)
}
var config unmarshallableConfig
if err := gcfg.ReadStringInto(&config, string(data)); err != nil {
if opts.WarnAsFatal {
return err
}
if err := gcfg.FatalOnly(err); err != nil {
return err
}

Check warning on line 194 in apis/v1alpha3/cloudprovider_encoding.go

View check run for this annotation

Codecov / codecov/patch

apis/v1alpha3/cloudprovider_encoding.go#L192-L194

Added lines #L192 - L194 were not covered by tests
}
c.Global = config.Global
c.Network = config.Network
c.Disk = config.Disk
c.Workspace = config.Workspace
c.Labels = config.Labels
c.VCenter = map[string]CPIVCenterConfig{}
for k, v := range config.VCenter {
c.VCenter[k] = *v
}
return nil
}

// IsEmpty returns true if an object is its empty value or if a struct, all of
// its fields are their empty values.
func IsEmpty(obj interface{}) bool {
return isEmpty(reflect.ValueOf(obj))

Check warning on line 211 in apis/v1alpha3/cloudprovider_encoding.go

View check run for this annotation

Codecov / codecov/patch

apis/v1alpha3/cloudprovider_encoding.go#L210-L211

Added lines #L210 - L211 were not covered by tests
}

// IsNotEmpty returns true when IsEmpty returns false.
func IsNotEmpty(obj interface{}) bool {
return !IsEmpty(obj)

Check warning on line 216 in apis/v1alpha3/cloudprovider_encoding.go

View check run for this annotation

Codecov / codecov/patch

apis/v1alpha3/cloudprovider_encoding.go#L215-L216

Added lines #L215 - L216 were not covered by tests
}

// isEmpty returns true if an object's fields are all set to their empty values.
func isEmpty(val reflect.Value) bool {
switch val.Kind() {
case reflect.Interface, reflect.Ptr:
return val.IsNil() || isEmpty(val.Elem())

case reflect.Struct:
structIsEmpty := true
for fieldIndex := 0; fieldIndex < val.NumField(); fieldIndex++ {
if structIsEmpty = isEmpty(val.Field(fieldIndex)); !structIsEmpty {
break
}
}
return structIsEmpty

case reflect.Array, reflect.String:
return val.Len() == 0

case reflect.Bool:
return !val.Bool()

case reflect.Map, reflect.Slice:
return val.IsNil() || val.Len() == 0

case reflect.Float32, reflect.Float64:
return val.Float() == 0

Check warning on line 244 in apis/v1alpha3/cloudprovider_encoding.go

View check run for this annotation

Codecov / codecov/patch

apis/v1alpha3/cloudprovider_encoding.go#L243-L244

Added lines #L243 - L244 were not covered by tests

case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return val.Int() == 0

default:
panic(errors.Errorf("invalid kind: %s", val.Kind()))

Check warning on line 250 in apis/v1alpha3/cloudprovider_encoding.go

View check run for this annotation

Codecov / codecov/patch

apis/v1alpha3/cloudprovider_encoding.go#L249-L250

Added lines #L249 - L250 were not covered by tests
}
}

// MarshalCloudProviderArgs marshals the cloud provider arguments for passing
// into a pod spec.
func (cpic *CPICloudConfig) MarshalCloudProviderArgs() []string {
args := []string{
"--v=2",
"--cloud-provider=vsphere",
"--cloud-config=/etc/cloud/vsphere.conf",
}
if cpic.ExtraArgs != nil {
for k, v := range cpic.ExtraArgs {
args = append(args, fmt.Sprintf("--%s=%s", k, v))
}

Check warning on line 265 in apis/v1alpha3/cloudprovider_encoding.go

View check run for this annotation

Codecov / codecov/patch

apis/v1alpha3/cloudprovider_encoding.go#L256-L265

Added lines #L256 - L265 were not covered by tests
}
return args

Check warning on line 267 in apis/v1alpha3/cloudprovider_encoding.go

View check run for this annotation

Codecov / codecov/patch

apis/v1alpha3/cloudprovider_encoding.go#L267

Added line #L267 was not covered by tests
}
Loading