Merge "Adds command objects for `cluster check-certificate-expiration`"

This commit is contained in:
Zuul 2020-11-11 10:09:30 +00:00 committed by Gerrit Code Review
commit 084ed45ef3
7 changed files with 289 additions and 15 deletions

View File

@ -17,8 +17,9 @@ package checkexpiration
import (
"github.com/spf13/cobra"
"opendev.org/airship/airshipctl/pkg/cluster/checkexpiration"
"opendev.org/airship/airshipctl/pkg/config"
"opendev.org/airship/airshipctl/pkg/errors"
"opendev.org/airship/airshipctl/pkg/k8s/client"
"opendev.org/airship/airshipctl/pkg/log"
)
@ -54,25 +55,31 @@ airshipctl cluster check-certificate-expiration -t 30 -o yaml --kubeconfig testc
// NewCheckCommand creates a new command for generating secret information
func NewCheckCommand(cfgFactory config.Factory) *cobra.Command {
var threshold int
var contentType, kubeconfig string
c := &checkexpiration.CheckCommand{
Options: checkexpiration.CheckFlags{},
CfgFactory: cfgFactory,
ClientFactory: client.DefaultClient,
}
checkCmd := &cobra.Command{
Use: "check-certificate-expiration",
Short: "Check for expiring TLS certificates, secrets and kubeconfigs in the kubernetes cluster",
Long: checkLong[1:],
Example: checkExample,
RunE: func(cmd *cobra.Command, args []string) error {
return errors.ErrNotImplemented{What: "check certificate expiration"}
return c.RunE(cmd.OutOrStdout())
},
}
checkCmd.Flags().IntVarP(&threshold, "threshold", "t", -1,
checkCmd.Flags().IntVarP(&c.Options.Threshold, "threshold", "t", -1,
"The max expiration threshold in days before a certificate is"+
" expiring. Displays all the certificates by default")
checkCmd.Flags().StringVarP(&contentType, "output", "o", "json", "Convert "+
checkCmd.Flags().StringVarP(&c.Options.FormatType, "output", "o", "json", "Convert "+
"output to yaml or json")
checkCmd.Flags().StringVar(&kubeconfig, kubeconfigFlag, "",
checkCmd.Flags().StringVar(&c.Options.Kubeconfig, kubeconfigFlag, "",
"Path to kubeconfig associated with cluster being managed")
checkCmd.Flags().StringVar(&c.Options.KubeContext, "kubecontext", "",
"Kubeconfig context to be used")
err := checkCmd.MarkFlagRequired(kubeconfigFlag)
if err != nil {

View File

@ -30,5 +30,6 @@ airshipctl cluster check-certificate-expiration -t 30 -o yaml --kubeconfig testc
Flags:
-h, --help help for check-certificate-expiration
--kubeconfig string Path to kubeconfig associated with cluster being managed
--kubecontext string Kubeconfig context to be used
-o, --output string Convert output to yaml or json (default "json")
-t, --threshold int The max expiration threshold in days before a certificate is expiring. Displays all the certificates by default (default -1)

View File

@ -42,6 +42,7 @@ airshipctl cluster check-certificate-expiration -t 30 -o yaml --kubeconfig testc
```
-h, --help help for check-certificate-expiration
--kubeconfig string Path to kubeconfig associated with cluster being managed
--kubecontext string Kubeconfig context to be used
-o, --output string Convert output to yaml or json (default "json")
-t, --threshold int The max expiration threshold in days before a certificate is expiring. Displays all the certificates by default (default -1)
```

View File

@ -0,0 +1,60 @@
/*
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
https://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 checkexpiration
import (
"opendev.org/airship/airshipctl/pkg/config"
"opendev.org/airship/airshipctl/pkg/errors"
"opendev.org/airship/airshipctl/pkg/k8s/client"
)
// CertificateExpirationStore is the customized client store
type CertificateExpirationStore struct {
Kclient client.Interface
Settings config.Factory
}
// Expiration captures expiration information of all expirable entities in the cluster
type Expiration struct{}
// NewStore returns an instance of a CertificateExpirationStore
func NewStore(cfgFactory config.Factory, clientFactory client.Factory,
kubeconfig string, _ string) (CertificateExpirationStore, error) {
airshipconfig, err := cfgFactory()
if err != nil {
return CertificateExpirationStore{}, err
}
// TODO (guhan) Allow kube context to be passed to client Factory
// 4th argument in NewStore takes kube context and is ignored for now.
// To be modified post #388. Refer to
// https://review.opendev.org/#/c/760501/7/pkg/cluster/checkexpiration/command.go@31
kclient, err := clientFactory(airshipconfig.LoadedConfigPath(), kubeconfig)
if err != nil {
return CertificateExpirationStore{}, err
}
return CertificateExpirationStore{
Kclient: kclient,
Settings: cfgFactory,
}, nil
}
// GetExpiringCertificates checks for the expiration data
// NOT IMPLEMENTED (guhan)
// TODO (guhan) check for TLS certificates, workload kubeconfig and node certificates
func (store CertificateExpirationStore) GetExpiringCertificates(expirationThreshold int) (Expiration, error) {
return Expiration{}, errors.ErrNotImplemented{What: "check certificate expiration logic"}
}

View File

@ -0,0 +1,73 @@
/*
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
https://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 checkexpiration
import (
"encoding/json"
"io"
"strings"
"opendev.org/airship/airshipctl/pkg/config"
"opendev.org/airship/airshipctl/pkg/k8s/client"
"opendev.org/airship/airshipctl/pkg/util/yaml"
)
// CheckFlags flags given for checking the expiration
type CheckFlags struct {
Threshold int
FormatType string
Kubeconfig string
KubeContext string
}
// CheckCommand check expiration command
type CheckCommand struct {
Options CheckFlags
CfgFactory config.Factory
ClientFactory client.Factory
}
// RunE is the implementation of check command
func (c *CheckCommand) RunE(w io.Writer) error {
if !strings.EqualFold(c.Options.FormatType, "json") && !strings.EqualFold(c.Options.FormatType, "yaml") {
return ErrInvalidFormat{RequestedFormat: c.Options.FormatType}
}
secretStore, err := NewStore(c.CfgFactory, c.ClientFactory, c.Options.Kubeconfig, c.Options.KubeContext)
if err != nil {
return err
}
expirationInfo, err := secretStore.GetExpiringCertificates(c.Options.Threshold)
if err != nil {
return err
}
if c.Options.FormatType == "yaml" {
err = yaml.WriteOut(w, expirationInfo)
if err != nil {
return err
}
} else {
buffer, err := json.MarshalIndent(expirationInfo, "", " ")
if err != nil {
return err
}
_, err = w.Write(buffer)
if err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,106 @@
/*
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
https://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 checkexpiration_test
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/runtime"
"opendev.org/airship/airshipctl/pkg/cluster/checkexpiration"
"opendev.org/airship/airshipctl/pkg/config"
"opendev.org/airship/airshipctl/pkg/k8s/client"
"opendev.org/airship/airshipctl/pkg/k8s/client/fake"
"opendev.org/airship/airshipctl/testutil"
)
const (
testNotImplementedErr = "not implemented: check certificate expiration logic"
)
func TestRunE(t *testing.T) {
tests := []struct {
testCaseName string
testErr string
checkFlags checkexpiration.CheckFlags
cfgFactory config.Factory
expectedOutput string
}{
{
testCaseName: "invalid-input-format",
cfgFactory: func() (*config.Config, error) {
return nil, nil
},
checkFlags: checkexpiration.CheckFlags{
Threshold: 0,
FormatType: "test-yaml",
},
testErr: checkexpiration.ErrInvalidFormat{RequestedFormat: "test-yaml"}.Error(),
},
{
testCaseName: "valid-input-format-json",
cfgFactory: func() (*config.Config, error) {
cfg, _ := testutil.InitConfig(t)
return cfg, nil
},
checkFlags: checkexpiration.CheckFlags{
Threshold: 5000,
FormatType: "json",
Kubeconfig: "",
},
testErr: testNotImplementedErr,
},
{
testCaseName: "valid-input-format-yaml",
cfgFactory: func() (*config.Config, error) {
cfg, _ := testutil.InitConfig(t)
return cfg, nil
},
checkFlags: checkexpiration.CheckFlags{
Threshold: 5000,
FormatType: "yaml",
},
testErr: testNotImplementedErr,
},
}
for _, tt := range tests {
t.Run(tt.testCaseName, func(t *testing.T) {
var objects []runtime.Object
// TODO (guhan) append a dummy object for testing
ra := fake.WithTypedObjects(objects...)
command := checkexpiration.CheckCommand{
Options: tt.checkFlags,
CfgFactory: tt.cfgFactory,
ClientFactory: func(_ string, _ string) (client.Interface, error) {
return fake.NewClient(ra), nil
},
}
var buffer bytes.Buffer
err := command.RunE(&buffer)
if tt.testErr != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), tt.testErr)
}
// TODO (guhan) add else part to check the actual vs expected o/p
})
}
}

View File

@ -0,0 +1,26 @@
/*
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
https://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 checkexpiration
import "fmt"
// ErrInvalidFormat is called when the user provides format other than yaml/json
type ErrInvalidFormat struct {
RequestedFormat string
}
func (e ErrInvalidFormat) Error() string {
return fmt.Sprintf("invalid output format specified %s. Allowed values are json|yaml", e.RequestedFormat)
}