Skip to content

Commit

Permalink
Support blueprint resource for validate sub command to kanctl (#1187)
Browse files Browse the repository at this point in the history
* Support blueprint resource for validate subcommand to kanctl

* Validate the mandatory arguments of a function

* Add comment for function name checks

* Add unit tests

* Add license header in new files

* Fix lint issues in CI

* Refactor render_test.go to fix CI

* Address review comments

* Add another flag in validate blueprint to specify functionVersion

* Make funcitonVersion flag optional

* Fix test that failed in CI

* Address minor review comment

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
viveksinghggits and mergify[bot] committed Jan 21, 2022
1 parent bfe366c commit e5a2867
Show file tree
Hide file tree
Showing 12 changed files with 323 additions and 38 deletions.
7 changes: 7 additions & 0 deletions pkg/app/bp.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
bp "github.com/kanisterio/kanister/pkg/blueprint"
"github.com/kanisterio/kanister/pkg/field"
"github.com/kanisterio/kanister/pkg/log"
"k8s.io/apimachinery/pkg/util/rand"
)

const (
Expand Down Expand Up @@ -59,6 +60,12 @@ func (b AppBlueprint) Blueprint() *crv1alpha1.Blueprint {
if err != nil {
log.Error().WithError(err).Print("Failed to read Blueprint", field.M{"app": b.App})
}

// set the name to a dynamically generated value
// so that the name wont conflict with the same application
// installed using other ways
bpr.ObjectMeta.Name = fmt.Sprintf("%s-%s", bpr.ObjectMeta.Name, rand.String(5))

if b.UseDevImages {
updateImageTags(bpr)
}
Expand Down
7 changes: 0 additions & 7 deletions pkg/blueprint/blueprint.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,8 @@ package blueprint

import (
"bytes"
"fmt"
"io/ioutil"

"k8s.io/apimachinery/pkg/util/rand"
"k8s.io/apimachinery/pkg/util/yaml"

crv1alpha1 "github.com/kanisterio/kanister/pkg/apis/cr/v1alpha1"
Expand All @@ -47,10 +45,5 @@ func ReadFromFile(path string) (*crv1alpha1.Blueprint, error) {
return nil, err
}

// set the name to a dynamically generated value
// so that the name wont conflict with the same application
// installed as part of k10
bp.ObjectMeta.Name = fmt.Sprintf("%s-%s", bp.ObjectMeta.Name, rand.String(5))

return &bp, err
}
50 changes: 50 additions & 0 deletions pkg/blueprint/validate/validate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2022 The Kanister 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 validate

import (
"fmt"

kanister "github.com/kanisterio/kanister/pkg"
crv1alpha1 "github.com/kanisterio/kanister/pkg/apis/cr/v1alpha1"
_ "github.com/kanisterio/kanister/pkg/function"
"github.com/kanisterio/kanister/pkg/param"
"github.com/kanisterio/kanister/pkg/utils"
)

// Do takes a blueprint and validates if the function names in phases are correct
// and all the required arguments for the kanister functions are provided. This doesn't
// check anything with template params yet.
func Do(bp *crv1alpha1.Blueprint, funcVersion string) error {
for name, action := range bp.Actions {
// GetPhases also checks if the function names referred in the action are correct
phases, err := kanister.GetPhases(*bp, name, funcVersion, param.TemplateParams{})
if err != nil {
utils.PrintStage(fmt.Sprintf("validation of action %s", name), utils.Fail)
return err
}

for i, phase := range phases {
// validate function's mandatory arguments
if err := phase.Validate(action.Phases[i].Args); err != nil {
utils.PrintStage(fmt.Sprintf("validation of phase %s in action %s", phase.Name(), name), utils.Fail)
return err
}
utils.PrintStage(fmt.Sprintf("validation of phase %s in action %s", phase.Name(), name), utils.Pass)
}
}

return nil
}
162 changes: 162 additions & 0 deletions pkg/blueprint/validate/validate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// Copyright 2022 The Kanister 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 validate

import (
"strings"
"testing"

. "gopkg.in/check.v1"

kanister "github.com/kanisterio/kanister/pkg"
crv1alpha1 "github.com/kanisterio/kanister/pkg/apis/cr/v1alpha1"
)

func Test(t *testing.T) { TestingT(t) }

type ValidateBlueprint struct{}

var _ = Suite(&ValidateBlueprint{})

func (v *ValidateBlueprint) TestValidate(c *C) {
for _, tc := range []struct {
phases []crv1alpha1.BlueprintPhase
err Checker
errContains string
}{
{
phases: []crv1alpha1.BlueprintPhase{
{
Func: "KubeTask",
Name: "00",
Args: map[string]interface{}{
"image": "",
},
},
{
Func: "KubeExec",
Name: "01",
Args: map[string]interface{}{
"namespace": "",
"command": "",
},
},
{
Func: "KubeExec",
Name: "01",
Args: map[string]interface{}{
"namespace": "",
"command": "",
"pod": "",
},
},
},
errContains: "Required arg missing: command",
err: NotNil,
},
{
phases: []crv1alpha1.BlueprintPhase{
{
Func: "KubeTask",
Name: "10",
Args: map[string]interface{}{
"image": "",
"command": "",
},
},
{
Func: "KubeExec",
Name: "11",
Args: map[string]interface{}{
"namespace": "",
"command": "",
"pod": "",
},
},
},
err: IsNil,
},
{
// function name is incorrect
phases: []crv1alpha1.BlueprintPhase{
{
Func: "KubeTasks",
Name: "20",
Args: map[string]interface{}{
"image": "",
"command": "",
},
},
{
Func: "KubeExec",
Name: "21",
Args: map[string]interface{}{
"namespace": "",
"command": "",
"pod": "",
},
},
},
errContains: "Requested function {KubeTasks} has not been registered",
err: NotNil,
},
{
phases: []crv1alpha1.BlueprintPhase{
{
Func: "PrepareData",
Name: "30",
Args: map[string]interface{}{
"namespace": "",
"image": "",
"command": "",
},
},
},
err: IsNil,
},
{
phases: []crv1alpha1.BlueprintPhase{
{
Func: "PrepareData",
Name: "40",
Args: map[string]interface{}{
"namespace": "",
"image": "",
},
},
},
errContains: "Required arg missing: command",
err: NotNil,
},
} {
bp := blueprint()
bp.Actions["backup"].Phases = tc.phases
err := Do(bp, kanister.DefaultVersion)
if err != nil {
c.Assert(strings.Contains(err.Error(), tc.errContains), Equals, true)
}
c.Assert(err, tc.err)
}
}

func blueprint() *crv1alpha1.Blueprint {
return &crv1alpha1.Blueprint{
Actions: map[string]*crv1alpha1.BlueprintAction{
"backup": {
Phases: []crv1alpha1.BlueprintPhase{},
},
},
}
}
36 changes: 36 additions & 0 deletions pkg/kanctl/blueprint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright 2022 The Kanister 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 kanctl

import (
"errors"

"github.com/kanisterio/kanister/pkg/blueprint"
"github.com/kanisterio/kanister/pkg/blueprint/validate"
)

func performBlueprintValidation(p *validateParams) error {
if p.filename == "" {
return errors.New("--name is not supported for blueprint resources, please specify blueprint manifest using -f.")
}

// read blueprint from specified file
bp, err := blueprint.ReadFromFile(p.filename)
if err != nil {
return err
}

return validate.Do(bp, p.functionVersion)
}
3 changes: 1 addition & 2 deletions pkg/kanctl/kanctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,8 @@ func Execute() {
if err := root.Execute(); err != nil {
if Verbose {
log.WithError(err).Print("Kanctl failed to execute")
} else {
log.Error().Print(err.Error())
}

os.Exit(1)
}
}
Expand Down
13 changes: 7 additions & 6 deletions pkg/kanctl/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/kanisterio/kanister/pkg/apis/cr/v1alpha1"
"github.com/kanisterio/kanister/pkg/client/clientset/versioned"
"github.com/kanisterio/kanister/pkg/secrets"
"github.com/kanisterio/kanister/pkg/utils"
"github.com/kanisterio/kanister/pkg/validate"
)

Expand Down Expand Up @@ -384,18 +385,18 @@ func performProfileValidation(p *validateParams) error {
func validateProfile(ctx context.Context, profile *v1alpha1.Profile, cli kubernetes.Interface, schemaValidationOnly bool, printFailStageOnly bool) error {
var err error
if err = validate.ProfileSchema(profile); err != nil {
printStage(schemaValidation, fail)
utils.PrintStage(schemaValidation, utils.Fail)
return err
}
if !printFailStageOnly {
printStage(schemaValidation, pass)
utils.PrintStage(schemaValidation, utils.Pass)
}

if profile.Location.Bucket != "" {
for _, d := range []string{regionValidation, readAccessValidation, writeAccessValidation} {
if schemaValidationOnly {
if !printFailStageOnly {
printStage(d, skip)
utils.PrintStage(d, utils.Skip)
}
continue
}
Expand All @@ -408,16 +409,16 @@ func validateProfile(ctx context.Context, profile *v1alpha1.Profile, cli kuberne
err = validate.WriteAccess(ctx, profile, cli)
}
if err != nil {
printStage(d, fail)
utils.PrintStage(d, utils.Fail)
return err
}
if !printFailStageOnly {
printStage(d, pass)
utils.PrintStage(d, utils.Pass)
}
}
}
if !printFailStageOnly {
printStage(fmt.Sprintf("All checks passed.. %s\n", pass), "")
utils.PrintStage(fmt.Sprintf("All checks passed.. %s\n", utils.Pass), "")
}
return nil
}
Expand Down
Loading

0 comments on commit e5a2867

Please sign in to comment.