-
Notifications
You must be signed in to change notification settings - Fork 111
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
adds unit tests fixes job templates; fixes unit tests adds job cli tests make install includes job add job help test troubleshoot cli tests fix job help test chasing missing framework modify job create/update in deploy; chase cli test errors mv job tests to new file try go integration tests add longer wait for framework list make job cluster-level; fix up tests reset app tests reset app tests clean up tests more; fix job.deploy usage rm extra assertion
- Loading branch information
1 parent
708a55f
commit 70fae56
Showing
19 changed files
with
880 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
#!/usr/bin/env bats | ||
|
||
# To run locally: | ||
# export KETCH_EXECUTABLE_PATH=<location of ketch binary> | ||
# assure you have a kubernetes cluster running w/ traefik, cert manager, etc. (see ketch getting started docs) | ||
# assure the ketch cli is compiled (make ketch) | ||
# assure you have bats installed locally (via apt, brew, etc.) | ||
# ./cli_tests/app.sh | ||
|
||
setup() { | ||
if [[ -z "${KETCH_EXECUTABLE_PATH}" ]]; then | ||
KETCH=$(pwd)/bin/ketch | ||
else | ||
KETCH="${KETCH_EXECUTABLE_PATH}" | ||
fi | ||
|
||
JOB_FRAMEWORK="jobframework" | ||
JOB_NAME="sample-job" | ||
} | ||
|
||
teardown() { | ||
rm -f job.yaml | ||
} | ||
|
||
@test "job help" { | ||
result="$($KETCH job --help)" | ||
echo "RECEIVED:" $result | ||
[[ $result =~ "deploy" ]] | ||
[[ $result =~ "list" ]] | ||
[[ $result =~ "export" ]] | ||
[[ $result =~ "remove" ]] | ||
} | ||
|
||
@test "job deploy with yaml file" { | ||
fwresult=$($KETCH framework add "$JOB_FRAMEWORK") | ||
echo "RECEIVED:" $fwresult | ||
[[ $fwresult =~ "Successfully added!" ]] | ||
|
||
cat << EOF > job.yaml | ||
name: "$JOB_NAME" | ||
version: v1 | ||
type: Job | ||
framework: "$JOB_FRAMEWORK" | ||
description: "cli test job" | ||
containers: | ||
- name: pi | ||
image: perl | ||
command: | ||
- "perl" | ||
- "-Mbignum=bpi" | ||
- "-wle" | ||
- "print bpi(2000)" | ||
EOF | ||
result=$($KETCH job deploy job.yaml) | ||
[[ $result =~ "Successfully added!" ]] | ||
|
||
dataRegex="$JOB_NAME[ \t]+v1[ \t]+$JOB_FRAMEWORK[ \t]+cli test job" | ||
result=$($KETCH job list $JOB_NAME) | ||
echo "RECEIVED:" $result | ||
[[ $result =~ $dataRegex ]] | ||
} | ||
|
||
@test "job list" { | ||
result=$($KETCH job list) | ||
headerRegex="NAME[ \t]+VERSION[ \t]+FRAMEWORK[ \t]+DESCRIPTION" | ||
dataRegex="$JOB_NAME[ \t]+v1[ \t]+$JOB_FRAMEWORK[ \t]+cli test job" | ||
echo "RECEIVED:" $result | ||
[[ $result =~ $headerRegex ]] | ||
[[ $result =~ $dataRegex ]] | ||
} | ||
|
||
@test "job export" { | ||
run $KETCH job export "$JOB_NAME" -f job.yaml | ||
result=$(cat job.yaml) | ||
echo "RECEIVED:" $result | ||
[[ $result =~ "name: $JOB_NAME" ]] | ||
[[ $result =~ "type: Job" ]] | ||
[[ $result =~ "framework: $JOB_FRAMEWORK" ]] | ||
} | ||
|
||
@test "job remove" { | ||
result=$($KETCH job remove "$JOB_NAME") | ||
echo "RECEIVED:" $result | ||
[[ $result =~ "Successfully removed!" ]] | ||
|
||
# clean up framework | ||
fwresult=$(echo "ketch-$JOB_FRAMEWORK" | $KETCH framework remove "$JOB_FRAMEWORK") | ||
echo "RECEIVED:" $fwresult | ||
[[ $fwresult =~ "Framework successfully removed!" ]] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package main | ||
|
||
import ( | ||
"io" | ||
|
||
"github.com/spf13/cobra" | ||
) | ||
|
||
const jobHelp = ` | ||
Manage jobs. | ||
` | ||
|
||
func newJobCmd(cfg config, out io.Writer) *cobra.Command { | ||
cmd := &cobra.Command{ | ||
Use: "job", | ||
Short: "Manage Jobs", | ||
Long: jobHelp, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
return cmd.Usage() | ||
}, | ||
} | ||
cmd.AddCommand(newJobListCmd(cfg, out)) | ||
cmd.AddCommand(newJobDeployCmd(cfg, out)) | ||
cmd.AddCommand(newJobRemoveCmd(cfg, out)) | ||
cmd.AddCommand(newJobExportCmd(cfg, out)) | ||
return cmd | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"os" | ||
|
||
"github.com/spf13/cobra" | ||
apierrors "k8s.io/apimachinery/pkg/api/errors" | ||
"k8s.io/apimachinery/pkg/types" | ||
"sigs.k8s.io/yaml" | ||
|
||
ketchv1 "github.com/shipa-corp/ketch/internal/api/v1beta1" | ||
) | ||
|
||
const jobDeployHelp = ` | ||
Deploy a job. | ||
` | ||
|
||
const ( | ||
defaultJobVersion = "v1" | ||
defaultJobParallelism = 1 | ||
defaultJobCompletions = 1 | ||
defaultJobBackoffLimit = 6 | ||
defaultJobRestartPolicy = "Never" | ||
) | ||
|
||
func newJobDeployCmd(cfg config, out io.Writer) *cobra.Command { | ||
cmd := &cobra.Command{ | ||
Use: "deploy [FILENAME]", | ||
Short: "Deploy a job.", | ||
Long: jobDeployHelp, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
filename := args[0] | ||
return jobDeploy(cmd.Context(), cfg, filename, out) | ||
}, | ||
} | ||
return cmd | ||
} | ||
|
||
func jobDeploy(ctx context.Context, cfg config, filename string, out io.Writer) error { | ||
b, err := os.ReadFile(filename) | ||
if err != nil { | ||
return err | ||
} | ||
var spec ketchv1.JobSpec | ||
err = yaml.Unmarshal(b, &spec) | ||
if err != nil { | ||
return err | ||
} | ||
setJobSpecDefaults(&spec) | ||
if err = validateJobSpec(&spec); err != nil { | ||
return err | ||
} | ||
|
||
var job ketchv1.Job | ||
err = cfg.Client().Get(ctx, types.NamespacedName{Name: spec.Name}, &job) | ||
if err != nil { | ||
if apierrors.IsNotFound(err) { | ||
job.ObjectMeta.Name = spec.Name | ||
job.Spec = spec | ||
if err := cfg.Client().Create(ctx, &job); err != nil { | ||
return fmt.Errorf("failed to create job: %w", err) | ||
} | ||
fmt.Fprintln(out, "Successfully added!") | ||
return nil | ||
} | ||
return err | ||
} | ||
job.Spec = spec | ||
if err := cfg.Client().Update(ctx, &job); err != nil { | ||
return fmt.Errorf("failed to create job: %w", err) | ||
} | ||
fmt.Fprintln(out, "Successfully updated!") | ||
return nil | ||
} | ||
|
||
// setJobSpecDefaults sets defaults on job.Spec for some unset fields | ||
func setJobSpecDefaults(jobSpec *ketchv1.JobSpec) { | ||
jobSpec.Type = "Job" | ||
if jobSpec.Version == "" { | ||
jobSpec.Version = defaultJobVersion | ||
} | ||
if jobSpec.Parallelism == 0 { | ||
jobSpec.Parallelism = defaultJobParallelism | ||
} | ||
if jobSpec.Completions == 0 && jobSpec.Parallelism > 1 { | ||
jobSpec.Completions = defaultJobCompletions | ||
} | ||
if jobSpec.BackoffLimit == 0 { | ||
jobSpec.BackoffLimit = defaultJobBackoffLimit | ||
} | ||
if jobSpec.Policy.RestartPolicy == "" { | ||
jobSpec.Policy.RestartPolicy = defaultJobRestartPolicy | ||
} | ||
} | ||
|
||
// validateJobSpec assures that required fields are populated. Missing fields will throw errors | ||
// when the custom resource is created, but this is a way to surface errors to user clearly. | ||
func validateJobSpec(jobSpec *ketchv1.JobSpec) error { | ||
if jobSpec.Name == "" || jobSpec.Framework == "" { | ||
return errors.New("job.name and job.framework are required") | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
package main | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"os" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
"k8s.io/apimachinery/pkg/runtime" | ||
"k8s.io/apimachinery/pkg/types" | ||
|
||
ketchv1 "github.com/shipa-corp/ketch/internal/api/v1beta1" | ||
"github.com/shipa-corp/ketch/internal/mocks" | ||
) | ||
|
||
func TestJobDeploy(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
jobName string | ||
cfg config | ||
filename string | ||
yamlData string | ||
wantJobSpec ketchv1.JobSpec | ||
wantOut string | ||
wantErr string | ||
}{ | ||
{ | ||
name: "job from yaml file", | ||
jobName: "hello", | ||
cfg: &mocks.Configuration{ | ||
CtrlClientObjects: []runtime.Object{}, | ||
DynamicClientObjects: []runtime.Object{}, | ||
}, | ||
filename: "job.yaml", | ||
yamlData: `name: hello | ||
version: v1 | ||
framework: myframework | ||
description: test | ||
parallelism: 1 | ||
completions: 1 | ||
suspend: false | ||
backoffLimit: 6 | ||
containers: | ||
- name: lister | ||
image: ubuntu | ||
command: | ||
- ls | ||
- / | ||
policy: | ||
restartPolicy: Never | ||
`, | ||
wantJobSpec: ketchv1.JobSpec{ | ||
Name: "hello", | ||
Version: "v1", | ||
Type: "Job", | ||
Framework: "myframework", | ||
Description: "test", | ||
Parallelism: 1, | ||
Completions: 1, | ||
Suspend: false, | ||
BackoffLimit: 6, | ||
Containers: []ketchv1.Container{ | ||
{ | ||
Name: "lister", | ||
Image: "ubuntu", | ||
Command: []string{"ls", "/"}, | ||
}, | ||
}, | ||
Policy: ketchv1.Policy{ | ||
RestartPolicy: "Never", | ||
}, | ||
}, | ||
wantOut: "Successfully added!\n", | ||
}, | ||
{ | ||
name: "error - validation fail", | ||
jobName: "hello", | ||
cfg: &mocks.Configuration{ | ||
CtrlClientObjects: []runtime.Object{}, | ||
DynamicClientObjects: []runtime.Object{}, | ||
}, | ||
filename: "job.yaml", | ||
yamlData: `version: v1 | ||
framework: NOFRAMEWORK | ||
description: test`, | ||
wantErr: "job.name and job.framework are required", | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
if tt.yamlData != "" { | ||
file, err := os.CreateTemp(t.TempDir(), "*.yaml") | ||
require.Nil(t, err) | ||
_, err = file.Write([]byte(tt.yamlData)) | ||
require.Nil(t, err) | ||
defer os.Remove(file.Name()) | ||
tt.filename = file.Name() | ||
} | ||
out := &bytes.Buffer{} | ||
err := jobDeploy(context.Background(), tt.cfg, tt.filename, out) | ||
if len(tt.wantErr) > 0 { | ||
require.NotNil(t, err) | ||
require.Equal(t, tt.wantErr, err.Error()) | ||
return | ||
} else { | ||
require.Nil(t, err) | ||
} | ||
require.Equal(t, tt.wantOut, out.String()) | ||
|
||
gotJob := ketchv1.Job{} | ||
err = tt.cfg.Client().Get(context.Background(), types.NamespacedName{Name: tt.jobName}, &gotJob) | ||
require.Nil(t, err) | ||
require.Equal(t, tt.wantJobSpec, gotJob.Spec) | ||
}) | ||
} | ||
} |
Oops, something went wrong.