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

Bug 1830270: cmd: add explain subcommand #3515

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .yamllint
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ rules:

ignore: |
vendor/
data/data/install.openshift.io_installconfigs.yaml
11 changes: 11 additions & 0 deletions cmd/openshift-install/explain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package main

import (
"github.com/spf13/cobra"

"github.com/openshift/installer/pkg/explain"
)

func newExplainCmd() *cobra.Command {
return explain.NewCmd()
}
1 change: 1 addition & 0 deletions cmd/openshift-install/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ func installerMain() {
newGraphCmd(),
newCompletionCmd(),
newMigrateCmd(),
newExplainCmd(),
} {
rootCmd.AddCommand(subCmd)
}
Expand Down
1,240 changes: 1,240 additions & 0 deletions data/data/install.openshift.io_installconfigs.yaml

Large diffs are not rendered by default.

41 changes: 41 additions & 0 deletions docs/dev/explain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# InstallConfig Explain

The installer allows it's users to see all the configuration available using the `explain` subcommand. The command works almost like the
the `oc explain` subcommand.

## Generating the documentation

Like `oc explain` and Custom Resource Definitions, the installer also generates an internal only Custom Resource Definition for the `InstallConfig`.

```sh
go generate ./pkg/types/installconfig.go
```

The code generation uses the upstream project kubebuilder, which provides tools like controller-tools to [generate][kubebuilder-generate-crd] Custom Resource Definitions.

## Descriptions of fields and various types

The generator uses the Godoc comments on the fields and types to create the descriptions. Therefore, making sure that everything used by the `InstallConfig` definition has user friendly comments will automatically transfer to user friendly explain output.

## Kubebuilder markers

The generator allows use of various markers for fields and types to provide information about the valid inputs. Various available markers are defined [here][kubebuilder-validation-markers]

## Additional guidelines on godoc comments

All definitions in installer codebase already follow and enforce [effective-go] guidelines, but for better explain output for the types these additional guidelines should also be followed
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing an anchor for effective-go?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wking fixed


1. No external types are allowed to be used in the `InstallConfig`. All the types used should be defined in the installer repository itself. The only exception is the `TypeMeta` and `ObjectMeta`.

2. Fields should always have `json` tags that follow [mixedCaps][go-mixed-caps] and should always start with lowercase.

3. The comments on the fields must use the `json` tag to reference the field.

4. Optional fields must have the `+optional` marker defined. Optional fields must also define the default value. If the values are static, `+kubebuilder:validation=Default` marker should be used, otherwise the comment must clearly define how the default value will be calculated.

5. Only string based enum types are allowed. Also such enum type must use `+kubebuilder:validation:Enum` marker.

[go-effective]: https://golang.org/doc/effective_go.html
[go-mixed-caps]: https://golang.org/doc/effective_go.html#mixed-caps
[kubebuilder-generate-crd]: https://book.kubebuilder.io/reference/generating-crd.html
[kubebuilder-validation-markers]: https://book.kubebuilder.io/reference/markers/crd-validation.html
7 changes: 5 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -115,14 +115,16 @@ require (
gopkg.in/AlecAivazis/survey.v1 v1.8.9-0.20200217094205-6773bdf39b7f
gopkg.in/ini.v1 v1.51.0
gopkg.in/yaml.v2 v2.2.8
k8s.io/api v0.18.0
k8s.io/apimachinery v0.18.0
k8s.io/api v0.18.2
k8s.io/apiextensions-apiserver v0.18.2
k8s.io/apimachinery v0.18.2
k8s.io/client-go v12.0.0+incompatible
k8s.io/klog v1.0.0
k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89
sigs.k8s.io/cluster-api-provider-aws v0.0.0
sigs.k8s.io/cluster-api-provider-azure v0.0.0
sigs.k8s.io/cluster-api-provider-openstack v0.0.0
sigs.k8s.io/controller-tools v0.3.0
)

replace (
Expand Down Expand Up @@ -164,4 +166,5 @@ replace (
sigs.k8s.io/cluster-api-provider-aws => github.com/openshift/cluster-api-provider-aws v0.2.1-0.20200316201703-923caeb1d0d8 // Pin OpenShift fork
sigs.k8s.io/cluster-api-provider-azure => github.com/openshift/cluster-api-provider-azure v0.1.0-alpha.3.0.20200120114645-8a9592f1f87b // Pin OpenShift fork
sigs.k8s.io/cluster-api-provider-openstack => github.com/openshift/cluster-api-provider-openstack v0.0.0-20200323110431-3311de91e078 // Pin OpenShift fork
sigs.k8s.io/controller-tools => github.com/abhinavdahiya/controller-tools v0.3.1-0.20200430222905-6fdf2d5fc069 // Using fork for sigs.k8s.io/controller-tools#427
)
16 changes: 2 additions & 14 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmx
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af h1:DBNMBMuMiWYu0b+8KMJuWmfCkcxl09JwdlqwDZZ6U14=
github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw=
github.com/abhinavdahiya/controller-tools v0.3.1-0.20200430222905-6fdf2d5fc069 h1:VGdRFAFdKKu/1kRTJJYPBGDkDK6ce5SNxbknnZTxAxI=
github.com/abhinavdahiya/controller-tools v0.3.1-0.20200430222905-6fdf2d5fc069/go.mod h1:enhtKGfxZD1GFEoMgP8Fdbu+uKQ/cq1/WGJhdVChfvI=
github.com/aclements/go-gg v0.0.0-20170323211221-abd1f791f5ee/go.mod h1:55qNq4vcpkIuHowELi5C8e+1yUHtoLoOUR9QU5j7Tes=
github.com/aclements/go-moremath v0.0.0-20190506201756-286cc0be6f75/go.mod h1:idZL3yvz4kzx1dsBOAC+oYv6L92P1oFEhUXUB1A/lwQ=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
Expand Down Expand Up @@ -1771,14 +1773,9 @@ github.com/openshift/api v0.0.0-20200413201024-c6e8c9b6eb9a h1:fIIKps4VKnxrXSp3l
github.com/openshift/api v0.0.0-20200413201024-c6e8c9b6eb9a/go.mod h1:RKMJ5CBnljLfnej+BJ/xnOWc3kZDvJUaIAEq2oKSPtE=
github.com/openshift/baremetal-operator v0.0.0-20200206190020-71b826cc0f0a h1:65ZuRkPnQGh9uo0z93KosrPlwEWJNxUjxnuM9lyGBHc=
github.com/openshift/baremetal-operator v0.0.0-20200206190020-71b826cc0f0a/go.mod h1:cXwn0hhgHpORjBasg0RnZwhKaJGy9+r6qgj0HCXrs/Y=
github.com/openshift/build-machinery-go v0.0.0-20200205161356-ef115f5adc73/go.mod h1:1CkcsT3aVebzRBzVTSbiKSkJMsC/CASqxesfqEMfJEc=
github.com/openshift/build-machinery-go v0.0.0-20200211121458-5e3d6e570160/go.mod h1:1CkcsT3aVebzRBzVTSbiKSkJMsC/CASqxesfqEMfJEc=
github.com/openshift/client-go v0.0.0-20190617165122-8892c0adc000/go.mod h1:6rzn+JTr7+WYS2E1TExP4gByoABxMznR6y2SnUIkmxk=
github.com/openshift/client-go v0.0.0-20191001081553-3b0e988f8cb0/go.mod h1:6rzn+JTr7+WYS2E1TExP4gByoABxMznR6y2SnUIkmxk=
github.com/openshift/client-go v0.0.0-20200116152001-92a2713fa240 h1:XYfJWv2Ch+qInGLDEedHRtDsJwnxyU1L8U7SY56NcA8=
github.com/openshift/client-go v0.0.0-20200116152001-92a2713fa240/go.mod h1:4riOwdj99Hd/q+iAcJZfNCsQQQMwURnZV6RL4WHYS5w=
github.com/openshift/client-go v0.0.0-20200320150128-a906f3d8e723 h1:FfrELmZ9N9NtVE15qmTRkJIETX75QHdr65xiuTKvNYo=
github.com/openshift/client-go v0.0.0-20200320150128-a906f3d8e723/go.mod h1:wNBSSt4RZTHhUWyhBE3gxTR32QpF9DB2SfS14u2IxuE=
github.com/openshift/cloud-credential-operator v0.0.0-20200316201045-d10080b52c9e h1:2gyl9UVyjHSWzdS56KUXxQwIhENbq2x2olqoMQSA/C8=
github.com/openshift/cloud-credential-operator v0.0.0-20200316201045-d10080b52c9e/go.mod h1:iPn+uhIe7nkP5BMHe2QnbLtg5m/AIQ1xvz9s3cig5ss=
github.com/openshift/cluster-api v0.0.0-20190805113604-f8de78af80fc/go.mod h1:mNsD1dsD4T57kV4/C6zTHke/Ro166xgnyyRZqkamiEU=
Expand Down Expand Up @@ -1811,12 +1808,9 @@ github.com/openshift/hashicorp-terraform-plugin-sdk v1.6.0-openshift/go.mod h1:H
github.com/openshift/imagebuilder v1.1.0/go.mod h1:9aJRczxCH0mvT6XQ+5STAQaPWz7OsWcU5/mRkt8IWeo=
github.com/openshift/library-go v0.0.0-20190619114638-6b58b672ee58/go.mod h1:NBttNjZpWwup/nthuLbPAPSYC8Qyo+BBK5bCtFoyYjo=
github.com/openshift/library-go v0.0.0-20191003152030-97c62d8a2901/go.mod h1:NBttNjZpWwup/nthuLbPAPSYC8Qyo+BBK5bCtFoyYjo=
github.com/openshift/library-go v0.0.0-20200210105614-4bf528465627 h1:Rs1RtB123VJr+kqXBYOTERNp23tZUUZ6w1gWrkroH3M=
github.com/openshift/library-go v0.0.0-20200210105614-4bf528465627/go.mod h1:T+sDdW3J/cgxUSqPdAwmhFrJhfFRv1ZtCSTVY59phN4=
github.com/openshift/library-go v0.0.0-20200324092245-db2a8546af81 h1:bNUcSdyoACkjI2USyvDbAMb6lCtghdz563b0bfhPC8A=
github.com/openshift/library-go v0.0.0-20200324092245-db2a8546af81/go.mod h1:Qc5duoXHzAKyUfA0REIlG/rdfWzknOpp9SiDiyg5Y7A=
github.com/openshift/machine-api-operator v0.0.0-20190312153711-9650e16c9880/go.mod h1:7HeAh0v04zQn1L+4ItUjvpBQYsm2Nf81WaZLiXTcnkc=
github.com/openshift/machine-api-operator v0.2.0 h1:g+EIEZrbbE0C2zC2x4nddgA2oxqP/3sjkEGdiX2qNe8=
github.com/openshift/machine-api-operator v0.2.1-0.20191128180243-986b771e661d/go.mod h1:9qQPF00anuIsc6RiHYfHE0+cZZImbvFNLln0NRBVVMg=
github.com/openshift/machine-api-operator v0.2.1-0.20200310180732-c63fa2b143f0 h1:Na0422T5qq9e4AtBqH4hyqujESg29Akrf2asy/kc02U=
github.com/openshift/machine-api-operator v0.2.1-0.20200310180732-c63fa2b143f0/go.mod h1:b3huCV+DbroXP1sHtsU5xBwx97zqc6GKB5owyl2zsNM=
Expand Down Expand Up @@ -2854,11 +2848,6 @@ sigs.k8s.io/controller-runtime v0.2.0/go.mod h1:ZHqrRDZi3f6BzONcvlUxkqCKgwasGk5F
sigs.k8s.io/controller-runtime v0.3.1-0.20191016212439-2df793d02076/go.mod h1:p2vzQ3RuSVv9YR4AcM0y8TKHQA+0oLXazKFt6Z0OdS8=
sigs.k8s.io/controller-runtime v0.4.0 h1:wATM6/m+3w8lj8FXNaO6Fs/rq/vqoOjO1Q116Z9NPsg=
sigs.k8s.io/controller-runtime v0.4.0/go.mod h1:ApC79lpY3PHW9xj/w9pj+lYkLgwAAUZwfXkME1Lajns=
sigs.k8s.io/controller-tools v0.2.2-0.20190919191502-76a25b63325a/go.mod h1:8SNGuj163x/sMwydREj7ld5mIMJu1cDanIfnx6xsU70=
sigs.k8s.io/controller-tools v0.2.2-0.20190930215132-4752ed2de7d2/go.mod h1:8SNGuj163x/sMwydREj7ld5mIMJu1cDanIfnx6xsU70=
sigs.k8s.io/controller-tools v0.2.4/go.mod h1:m/ztfQNocGYBgTTCmFdnK94uVvgxeZeE3LtJvd/jIzA=
sigs.k8s.io/controller-tools v0.2.9-0.20200331153640-3c5446d407dd h1:q2VrviTVgpbV4H+J8XjXnFtuGCkgED3M9tcykEgN7c4=
sigs.k8s.io/controller-tools v0.2.9-0.20200331153640-3c5446d407dd/go.mod h1:D2LzYpGDYjxaAALDVYAwaqaKp2fNuyO5yfOBoU/cbBE=
sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
sigs.k8s.io/structured-merge-diff v0.0.0-20190302045857-e85c7b244fd2/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
Expand All @@ -2868,7 +2857,6 @@ sigs.k8s.io/testing_frameworks v0.1.1/go.mod h1:VVBKrHmJ6Ekkfz284YKhQePcdycOzNH9
sigs.k8s.io/testing_frameworks v0.1.2-0.20190130140139-57f07443c2d4/go.mod h1:VVBKrHmJ6Ekkfz284YKhQePcdycOzNH9qL6ht1zEr/U=
sigs.k8s.io/testing_frameworks v0.1.2 h1:vK0+tvjF0BZ/RYFeZ1E6BYBwHJJXhjuZ3TdsEKH+UQM=
sigs.k8s.io/testing_frameworks v0.1.2/go.mod h1:ToQrwSC3s8Xf/lADdZp3Mktcql9CG0UAmdJG9th5i0w=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
Expand Down
87 changes: 87 additions & 0 deletions pkg/explain/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package explain

import (
"io/ioutil"
"os"
"strings"

"github.com/openshift/installer/data"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)

// NewCmd returns a subcommand for explain
func NewCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "explain",
Short: "List the fields for supported InstallConfig versions",
Long: `This command describes the fields associated with each supported InstallConfig API. Fields are identified via a simple
JSONPath identifier:

installconfig.<fieldName>[.<fieldName>]
`,
Example: `
# Get the documentation of the resource and its fields
kubectl explain installconfig

# Get the documentation of a AWS platform
kubectl explain installconfig.platform.aws`,
RunE: runCmd,
}

return cmd
}

func runCmd(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.Errorf("You must specify the type of resource to explain\n")
}
if len(args) > 1 {
return errors.Errorf("We accept only this format: explain RESOURCE\n")
}

file, err := data.Assets.Open(installConfigCRDFileName)
if err != nil {
return errors.Wrap(err, "failed to load InstallConfig CRD")
}
defer file.Close()

raw, err := ioutil.ReadAll(file)
if err != nil {
return errors.Wrap(err, "failed to read InstallConfig CRD")
}

resource, path := splitDotNotation(args[0])
if resource != "installconfig" {
return errors.Errorf("only installconfig resource is supported")
}

schema, err := loadSchema(raw)
if err != nil {
return errors.Wrap(err, "failed to load schema")
}

fschema, err := lookup(schema, path)
if err != nil {
return errors.Wrapf(err, "failed to load schema for the field %s", strings.Join(path, "."))
}

p := printer{Writer: os.Stdout}
p.PrintKindAndVersion()
p.PrintResource(fschema)
p.PrintFields(fschema)
return nil
}

func splitDotNotation(model string) (string, []string) {
var fieldsPath []string

// ignore trailing period
model = strings.TrimSuffix(model, ".")

dotModel := strings.Split(model, ".")
if len(dotModel) >= 1 {
fieldsPath = dotModel[1:]
}
return dotModel[0], fieldsPath
}
14 changes: 14 additions & 0 deletions pkg/explain/data_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package explain

import (
"io/ioutil"
"testing"
)

func loadCRD(t *testing.T) []byte {
crd, err := ioutil.ReadFile("../../data/data/install.openshift.io_installconfigs.yaml")
if err != nil {
t.Fatalf("failed to load CRD: %v", err)
}
return crd
}
1 change: 1 addition & 0 deletions pkg/explain/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package explain
5 changes: 5 additions & 0 deletions pkg/explain/explain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package explain

const (
installConfigCRDFileName = "install.openshift.io_installconfigs.yaml"
)
26 changes: 26 additions & 0 deletions pkg/explain/fields_lookup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package explain

import (
"github.com/pkg/errors"
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
)

func lookup(schema *apiextv1.JSONSchemaProps, path []string) (*apiextv1.JSONSchemaProps, error) {
if len(path) == 0 {
return schema, nil
}

properties := map[string]apiextv1.JSONSchemaProps{}
if schema.Items != nil && schema.Items.Schema != nil {
properties = schema.Items.Schema.Properties
}
if len(schema.Properties) > 0 {
properties = schema.Properties
}

property, ok := properties[path[0]]
if !ok {
return nil, errors.Errorf("invalid field %s, no such property found", path[0])
}
return lookup(&property, path[1:])
}
62 changes: 62 additions & 0 deletions pkg/explain/fields_lookup_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package explain

import (
"testing"

"github.com/stretchr/testify/assert"
)

func Test_lookup(t *testing.T) {
schema, err := loadSchema(loadCRD(t))
assert.NoError(t, err)

cases := []struct {
path []string

desc string
err string
}{{
desc: `InstallConfig is the configuration for an OpenShift install.`,
}, {
path: []string{"publish"},
desc: `Publish controls how the user facing endpoints of the cluster like the Kubernetes API, OpenShift routes etc. are exposed. When no strategy is specified, the strategy is "External".`,
}, {
path: []string{"publish", "unknown"},
err: `invalid field unknown, no such property found`,
}, {
path: []string{"platform"},
desc: `Platform is the configuration for the specific platform upon which to perform the installation.`,
}, {
path: []string{"platform", "aws"},
desc: `AWS is the configuration used when installing on AWS.`,
}, {
path: []string{"platform", "azure"},
desc: `Azure is the configuration used when installing on Azure.`,
}, {
path: []string{"platform", "aws", "region"},
desc: `Region specifies the AWS region where the cluster will be created.`,
}, {
path: []string{"platform", "aws", "subnets"},
desc: `Subnets specifies existing subnets (by ID) where cluster resources will be created. Leave unset to have the installer create subnets in a new VPC on your behalf.`,
}, {
path: []string{"platform", "aws", "userTags"},
desc: `UserTags additional keys and values that the installer will add as tags to all resources that it creates. Resources created by the cluster itself may not include these tags.`,
}, {
path: []string{"platform", "aws", "serviceEndpoints"},
desc: `ServiceEndpoints list contains custom endpoints which will override default service endpoint of AWS Services. There must be only one ServiceEndpoint for a service.`,
}, {
path: []string{"platform", "aws", "serviceEndpoints", "url"},
desc: `URL is fully qualified URI with scheme https, that overrides the default generated endpoint for a client. This must be provided and cannot be empty.`,
}}
for _, test := range cases {
t.Run("", func(t *testing.T) {
got, err := lookup(schema, test.path)
if test.err == "" {
assert.NoError(t, err)
assert.Equal(t, test.desc, got.Description)
} else {
assert.Regexp(t, test.err, err)
}
})
}
}