Skip to content

Commit

Permalink
Merge branch 'master' into chore/remove-unused-param
Browse files Browse the repository at this point in the history
  • Loading branch information
cPu1 committed Jun 30, 2020
2 parents e69f69b + 801a4bd commit c94d848
Show file tree
Hide file tree
Showing 11 changed files with 352 additions and 75 deletions.
11 changes: 10 additions & 1 deletion pkg/apis/eksctl.io/v1alpha5/assets/schema.json
Expand Up @@ -840,7 +840,16 @@
"default": "80"
},
"volumeType": {
"type": "string"
"type": "string",
"description": "Valid variants are: `\"gp2\"` is General Purpose SSD (default), `\"io1\"` is Provisioned IOPS SSD, `\"sc1\"` is Throughput Optimized HDD, `\"st1\"` is Cold HDD.",
"x-intellij-html-description": "Valid variants are: <code>&quot;gp2&quot;</code> is General Purpose SSD (default), <code>&quot;io1&quot;</code> is Provisioned IOPS SSD, <code>&quot;sc1&quot;</code> is Throughput Optimized HDD, <code>&quot;st1&quot;</code> is Cold HDD.",
"default": "gp2",
"enum": [
"gp2",
"io1",
"sc1",
"st1"
]
}
},
"preferredOrder": [
Expand Down
6 changes: 3 additions & 3 deletions pkg/apis/eksctl.io/v1alpha5/schema.go

Large diffs are not rendered by default.

22 changes: 13 additions & 9 deletions pkg/apis/eksctl.io/v1alpha5/types.go
Expand Up @@ -127,15 +127,6 @@ const (
// DefaultNodeCount defines the default number of nodes to be created
DefaultNodeCount = 2

// NodeVolumeTypeGP2 is General Purpose SSD
NodeVolumeTypeGP2 = "gp2"
// NodeVolumeTypeIO1 is Provisioned IOPS SSD
NodeVolumeTypeIO1 = "io1"
// NodeVolumeTypeSC1 is Throughput Optimized HDD
NodeVolumeTypeSC1 = "sc1"
// NodeVolumeTypeST1 is Cold HDD
NodeVolumeTypeST1 = "st1"

// DefaultNodeImageFamily defines the default image family for the worker nodes
DefaultNodeImageFamily = NodeImageFamilyAmazonLinux2
// NodeImageFamilyAmazonLinux2 represents Amazon Linux 2 family
Expand Down Expand Up @@ -228,6 +219,18 @@ const (
eksResourceAccountUSGovEast1 = "151742754352"
)

// Values for `VolumeType`
const (
// NodeVolumeTypeGP2 is General Purpose SSD (default)
NodeVolumeTypeGP2 = "gp2"
// NodeVolumeTypeIO1 is Provisioned IOPS SSD
NodeVolumeTypeIO1 = "io1"
// NodeVolumeTypeSC1 is Throughput Optimized HDD
NodeVolumeTypeSC1 = "sc1"
// NodeVolumeTypeST1 is Cold HDD
NodeVolumeTypeST1 = "st1"
)

// NodeGroupType defines the nodegroup type
type NodeGroupType string

Expand Down Expand Up @@ -699,6 +702,7 @@ type NodeGroup struct {
// Defaults to `80`
// +optional
VolumeSize *int `json:"volumeSize,omitempty"`
// Valid variants are `VolumeType` constants
// +optional
VolumeType *string `json:"volumeType,omitempty"`
// +optional
Expand Down
65 changes: 40 additions & 25 deletions pkg/schema/definition/comment.go
Expand Up @@ -13,38 +13,38 @@ var (
regexpExample = regexp.MustCompile("(.*)For example: `(.*)`")
typeOverridePattern = regexp.MustCompile("(.*)Schema type is `([a-zA-Z]+)`")
pTags = regexp.MustCompile("(<p>)|(</p>)")

// patterns for enum-type values
enumValuePattern = "^[ \t]*`(?P<name>[^`]+)`([ \t]*\\(default\\))?: .*$"
regexpEnumDefinition = regexp.MustCompile("(?m).*Valid [a-z]+ are((\\n" + enumValuePattern + ")*)")
regexpEnumValues = regexp.MustCompile("(?m)" + enumValuePattern)
)

func getTypeName(rawName string) string {
splits := strings.Split(rawName, ".")
return splits[len(splits)-1]
func interpretReference(ref string) (string, string) {
splits := strings.Split(ref, ".")
var pkg string
if len(splits) > 1 {
pkg = strings.Join(splits[:len(splits)-1], "")
}
return pkg, splits[len(splits)-1]
}

// HandleComment interprets as much as it can from the comment and saves this
// handleComment interprets as much as it can from the comment and saves this
// information in the Definition
func HandleComment(rawName, comment string, def *Definition, strict bool) (bool, error) {
func (dg *Generator) handleComment(rawName, comment string, def *Definition) (bool, error) {
var noDerive bool
name := getTypeName(rawName)
if strict && name != "" {
_, name := interpretReference(rawName)
if dg.Strict && name != "" {
if !strings.HasPrefix(comment, name+" ") {
return noDerive, errors.Errorf("comment should start with field name on field %s", name)
}
}

// process enums before stripping out newlines
if m := regexpEnumDefinition.FindStringSubmatch(comment); m != nil {
enums := make([]string, 0)
if n := regexpEnumValues.FindAllStringSubmatch(m[1], -1); n != nil {
for _, matches := range n {
enums = append(enums, matches[1])
}
def.Enum = enums
}
enumInformation, err := interpretEnumComments(dg.Importer, comment)
if err != nil {
return noDerive, err
}
var enumComment string
if enumInformation != nil {
def.Default = enumInformation.Default
def.Enum = enumInformation.Enum
comment = enumInformation.RemainingComment
enumComment = enumInformation.EnumComment
}

// Remove kubernetes-style annotations from comments
Expand Down Expand Up @@ -77,20 +77,35 @@ func HandleComment(rawName, comment string, def *Definition, strict bool) (bool,
}

// Remove type prefix
description = regexp.MustCompile("^"+name+" (\\*.*\\* )?((is (the )?)|(are (the )?)|(lists ))?").ReplaceAllString(description, "$1")
description = removeTypeNameFromComment(name, description)

if strict && name != "" {
if dg.Strict && name != "" {
if description == "" {
return noDerive, errors.Errorf("no description on field %s", name)
}
if !strings.HasSuffix(description, ".") {
return noDerive, errors.Errorf("description should end with a dot on field %s", name)
}
}
def.Description = description
def.Description = joinIfNotEmpty(" ", description, enumComment)

// Convert to HTML
html := string(blackfriday.Run([]byte(description), blackfriday.WithNoExtensions()))
html := string(blackfriday.Run([]byte(def.Description), blackfriday.WithNoExtensions()))
def.HTMLDescription = strings.TrimSpace(pTags.ReplaceAllString(html, ""))
return noDerive, nil
}

func removeTypeNameFromComment(name, description string) string {
return regexp.MustCompile("^"+name+" (\\*.*\\* )?((is (the )?)|(are (the )?)|(lists ))?").ReplaceAllString(description, "$1")
}

// joinIfNotEmpty is sadly necessary
func joinIfNotEmpty(sep string, elems ...string) string {
var nonEmptyElems = []string{}
for _, e := range elems {
if e != "" {
nonEmptyElems = append(nonEmptyElems, e)
}
}
return strings.Join(nonEmptyElems, sep)
}
21 changes: 15 additions & 6 deletions pkg/schema/definition/comment_test.go
Expand Up @@ -3,26 +3,35 @@ package definition
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/weaveworks/eksctl/pkg/schema/importer"
)

var _ = Describe("HandleComment", func() {
It("interprets type override", func() {
def := &Definition{}
comment := `Struct holds some info
Schema type is ` + "`string`"
noderive, err := HandleComment("Struct", comment, def, false)
dummy := func(path string) (importer.PackageInfo, error) {
return importer.PackageInfo{}, nil
}
dg := Generator{Strict: false, Importer: dummy}
noderive, err := dg.handleComment("Struct", comment, def)
Expect(err).ToNot(HaveOccurred())
Expect(noderive).To(BeTrue())
Expect(def.Description).To(Equal("holds some info"))
Expect(def.Type).To(Equal("string"))
})
})

var _ = Describe("GetTypeName", func() {
It("handles imported types", func() {
Expect(getTypeName("some/pkg.Thing")).To(Equal("Thing"))
var _ = Describe("interpretReference", func() {
It("interprets root name", func() {
pkg, name := interpretReference("SomeType")
Expect(name).To(Equal("SomeType"))
Expect(pkg).To(Equal(""))
})
It("handles in scope types", func() {
Expect(getTypeName("Thing")).To(Equal("Thing"))
It("interprets pkg name", func() {
pkg, name := interpretReference("some/pkg.SomeType")
Expect(name).To(Equal("SomeType"))
Expect(pkg).To(Equal("some/pkg"))
})
})
9 changes: 4 additions & 5 deletions pkg/schema/definition/definition.go
Expand Up @@ -17,14 +17,13 @@ const (
type Generator struct {
Strict bool
Definitions map[string]*Definition
PkgScope *ast.Scope
Importer importer.Importer
}

// newStructDefinition handles saving definitions for refs in the map
func (dg *Generator) newStructDefinition(name string, typeSpec ast.Expr, comment string) *Definition {
def := Definition{}
noDerive, err := HandleComment(name, comment, &def, dg.Strict)
noDerive, err := dg.handleComment(name, comment, &def)
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -89,9 +88,9 @@ func (dg *Generator) newPropertyRef(referenceName string, t ast.Expr, comment st
switch tt := t.(type) {
case *ast.Ident:
typeName := tt.Name
if obj, ok := dg.PkgScope.Objects[typeName]; ok {
if obj, ok := dg.Importer.FindPkgObj(typeName); ok {
// If we have a declared type behind our ident, add it
refTypeName, refTypeSpec = typeName, obj.Decl.(*ast.TypeSpec)
refTypeName, refTypeSpec = tt.Name, obj.Decl.(*ast.TypeSpec)
}
def = &Definition{}
setTypeOrRef(def, typeName)
Expand Down Expand Up @@ -145,7 +144,7 @@ func (dg *Generator) newPropertyRef(referenceName string, t ast.Expr, comment st
dg.Definitions[refTypeName] = structDef
}

_, err := HandleComment(referenceName, comment, def, dg.Strict)
_, err := dg.handleComment(referenceName, comment, def)
if err != nil {
panic(err)
}
Expand Down
143 changes: 143 additions & 0 deletions pkg/schema/definition/enum.go
@@ -0,0 +1,143 @@
package definition

import (
"fmt"
"go/ast"
"go/token"
"regexp"
"strconv"
"strings"

"github.com/pkg/errors"
"github.com/weaveworks/eksctl/pkg/schema/importer"
)

// patterns for enum-type values
var (
enumValuePattern = "^[ \t]*`(?P<name>[^`]+)`([ \t]*\\(default\\))?(?:(: .*)|,)?$"
regexpEnumReference = regexp.MustCompile("(?m).*Valid [a-z]+ are `(.*)` [a-z]+")
regexpEnumDefinition = regexp.MustCompile("(?m).*Valid [a-z]+ are:((\\n" + enumValuePattern + ")*)")
regexpEnumValues = regexp.MustCompile("(?m)" + enumValuePattern)
)

// findLiteralValue does a lookup for constant values
// at the moment it treats everything as a string
func findLiteralValue(importer importer.Importer, name string) (string, error) {
obj, ok := importer.FindPkgObj(name)
if !ok {
return "", errors.New("Not in package")
}
valueSpec, ok := obj.Decl.(*ast.ValueSpec)
if !ok {
return "", errors.New("obj must refer to a value")
}
basicLit, ok := valueSpec.Values[0].(*ast.BasicLit)
if !ok {
return "", errors.New("obj must have a literal value")
}
switch basicLit.Kind {
case token.STRING, token.CHAR:
str, err := strconv.Unquote(basicLit.Value)
if err != nil {
panic("Couldn't unquote basic literal of type STRING or CHAR")
}
return str, nil
default:
return basicLit.Value, nil
}
}

// enumCommentInformation holds interpreted information
type enumCommentInformation struct {
Enum []string
Default string
EnumComment string
RemainingComment string
}

func interpretReferencedVariantComment(importer importer.Importer, valueSpec *ast.ValueSpec) (def bool, value string, comment string) {
name := valueSpec.Names[0].Name
value, err := findLiteralValue(importer, name)
if err != nil {
panic("Couldn't find a value from an Ident, impossible!")
}
enum := value
if strings.Contains(valueSpec.Doc.Text(), "(default)") {
def = true
}
// valueSpec comments have a trailing newline
trimmed := strings.TrimSuffix(valueSpec.Doc.Text(), "\n")
// Replace with the real literal
generatedComment := strings.ReplaceAll(trimmed, valueSpec.Names[0].Name, fmt.Sprintf("`\"%s\"`", value))
return def, enum, generatedComment
}

// interpretEnumComments handles interpreting enum information from comments
func interpretEnumComments(importer importer.Importer, comment string) (*enumCommentInformation, error) {
// process enums before stripping out newlines
var def string
var enum = []string{}
if m := regexpEnumReference.FindStringSubmatch(comment); m != nil {
comment = strings.ReplaceAll(comment, m[0], "")
// We need to generate a comment here
var variantComments = []string{}
pkgName, variantName := interpretReference(m[1])

pkgInfo, err := importer(pkgName)
if err != nil {
return nil, err
}
variants, ok := pkgInfo.Variants[variantName]
if !ok {
return nil, errors.Errorf("Couldn't find %s in root package", variantName)
}
for _, valueSpec := range variants {
isDefault, value, variantComment := interpretReferencedVariantComment(importer, valueSpec)

enum = append(enum, value)
if isDefault {
def = value
}
variantComments = append(variantComments, variantComment)
}
joinedVariantComments := strings.Join(variantComments, ", ")
enumComment := strings.Join(append([]string{"Valid variants are:"}, joinedVariantComments), " ") + "."
return &enumCommentInformation{
Enum: enum,
Default: def,
EnumComment: enumComment,
RemainingComment: comment,
}, nil
} else if m := regexpEnumDefinition.FindStringSubmatch(comment); m != nil {
if n := regexpEnumValues.FindAllStringSubmatch(m[1], -1); n != nil {
for _, matches := range n {
rawVal := matches[1]
isDefault := matches[2] != ""
var enumVal string
if literal, err := strconv.Unquote(rawVal); err == nil {
enumVal = literal
} else {
val, err := findLiteralValue(importer, rawVal)
if err != nil {
return nil, errors.Wrapf(err, "couldn't resolve %s in package", rawVal)
}
enumVal = val
comment = strings.ReplaceAll(comment, rawVal, fmt.Sprintf(`"%s"`, val))
}
if isDefault {
def = enumVal
}
enum = append(enum, enumVal)
}
}
return &enumCommentInformation{
Enum: enum,
Default: def,
// For now the generated enum comment is empty
// because we leave the comment as is
EnumComment: "",
RemainingComment: comment,
}, nil
}
return nil, nil
}

0 comments on commit c94d848

Please sign in to comment.