Skip to content

Commit bab5b9c

Browse files
Jeff McCormickjoelanford
Jeff McCormick
andauthored
Scorecard2 pod execution (#2890)
* add build of scorecard-test image, wire it into the scorecard-test image, update scorecard-test to unzip a bundle from the expected mount point * add a sample config file and pod spec, plus remove a log message from the test image * add start of pod definition * split out kubeclient and testpod * add random string to test pod names * add interim pod log capture * add output formatting * split out CR helper * add suggestion for test names if user enters invalid test name in their configuration * convert all output to ScorecardOutput * add initial delete pod defer * make the bundle singular, refactor the CRs into a helper * add tar and untar of configmap * add cleanup flag * update kube client logic to look in various places for the config * add waittime command flag, add pod wait logic * change waittime to wait-time for the flag name * refactor some code, create a relation between the test and the test pod * fix get bundle to check for valid paths to avoid errors in the api call * refactor for linter and general cleanup * refactor the TestBundle into just a registry.Bundle * remove basic check status test as its not useful * remove reference to basic check status test which was removed * fix tests * remove basic status test from config * updates based on pr review comments * add build of scorecard-test image, wire it into the scorecard-test image, update scorecard-test to unzip a bundle from the expected mount point * add a sample config file and pod spec, plus remove a log message from the test image * add start of pod definition * split out kubeclient and testpod * add random string to test pod names * add interim pod log capture * add output formatting * split out CR helper * add suggestion for test names if user enters invalid test name in their configuration * convert all output to ScorecardOutput * add initial delete pod defer * make the bundle singular, refactor the CRs into a helper * add tar and untar of configmap * add cleanup flag * update kube client logic to look in various places for the config * add waittime command flag, add pod wait logic * change waittime to wait-time for the flag name * refactor some code, create a relation between the test and the test pod * fix get bundle to check for valid paths to avoid errors in the api call * refactor for linter and general cleanup * refactor the TestBundle into just a registry.Bundle * remove basic check status test as its not useful * remove reference to basic check status test which was removed * fix tests * remove basic status test from config * updates based on pr review comments * Update cmd/operator-sdk/alpha/scorecard/cmd.go Co-Authored-By: Joe Lanford <[email protected]> * Update cmd/operator-sdk/alpha/scorecard/cmd.go Co-Authored-By: Joe Lanford <[email protected]> * Update internal/scorecard/alpha/tests/test_bundle.go Co-Authored-By: Joe Lanford <[email protected]> * Update internal/scorecard/alpha/tests/test_bundle.go Co-Authored-By: Joe Lanford <[email protected]> * make updates based on PR reviews * refactor output formatting into the cmd * fix linter issue * rename tar function name * Update cmd/operator-sdk/alpha/scorecard/cmd.go doh! Co-Authored-By: Joe Lanford <[email protected]> * fix skipcleanup * refactor crhelper to avoid extraneous unmarshalling * replace fmt.Print with log.Error in a few cases * updates from PR review * Update internal/scorecard/alpha/tests/crhelper.go Co-Authored-By: Joe Lanford <[email protected]> * Update internal/scorecard/alpha/tests/crhelper.go Co-Authored-By: Joe Lanford <[email protected]> * Update cmd/operator-sdk/alpha/scorecard/cmd.go Co-Authored-By: Joe Lanford <[email protected]> * Update internal/scorecard/alpha/config.go Co-Authored-By: Joe Lanford <[email protected]> * rename ScorecardTest to Test and other PR review comments * various updates from PR comments * cleanup fmt.Error calls * refactor Options struct into Scorecard struct and add struct methods for it * make entrypoints a collection in the config file format * fix GetBundle return values * fix unit tests * fix image Co-authored-by: Joe Lanford <[email protected]>
1 parent cb85478 commit bab5b9c

File tree

22 files changed

+1014
-196
lines changed

22 files changed

+1014
-196
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ tags
9797
test/ansible-operator/ansible-operator
9898
test/helm-operator/helm-operator
9999
images/scorecard-proxy/scorecard-proxy
100+
images/scorecard-test/scorecard-test
100101

101102
# Test artifacts
102103
pkg/ansible/runner/testdata/valid.yaml

cmd/operator-sdk/alpha/scorecard/cmd.go

+80-15
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,29 @@
1515
package scorecard
1616

1717
import (
18+
"encoding/json"
1819
"fmt"
19-
"log"
20+
21+
"time"
2022

2123
scorecard "github.com/operator-framework/operator-sdk/internal/scorecard/alpha"
24+
"github.com/operator-framework/operator-sdk/pkg/apis/scorecard/v1alpha2"
2225
"github.com/spf13/cobra"
2326
"k8s.io/apimachinery/pkg/labels"
2427
)
2528

2629
func NewCmd() *cobra.Command {
2730
var (
28-
config string
29-
//bundle string // TODO - to be implemented
30-
selector string
31-
//list bool // TODO - to be implemented
31+
config string
32+
outputFormat string
33+
bundle string
34+
selector string
35+
kubeconfig string
36+
namespace string
37+
serviceAccount string
38+
list bool
39+
skipCleanup bool
40+
waitTime time.Duration
3241
)
3342
scorecardCmd := &cobra.Command{
3443
Use: "scorecard",
@@ -38,31 +47,87 @@ func NewCmd() *cobra.Command {
3847
RunE: func(cmd *cobra.Command, args []string) error {
3948

4049
var err error
41-
o := scorecard.Options{}
50+
o := scorecard.Scorecard{
51+
ServiceAccount: serviceAccount,
52+
Namespace: namespace,
53+
BundlePath: bundle,
54+
SkipCleanup: skipCleanup,
55+
WaitTime: waitTime,
56+
}
57+
58+
if bundle == "" {
59+
return fmt.Errorf("bundle flag required")
60+
}
61+
62+
o.Client, err = scorecard.GetKubeClient(kubeconfig)
63+
if err != nil {
64+
return fmt.Errorf("could not get kubernetes client: %w", err)
65+
}
66+
4267
o.Config, err = scorecard.LoadConfig(config)
4368
if err != nil {
44-
return fmt.Errorf("could not find config file %s", err.Error())
69+
return fmt.Errorf("could not find config file %w", err)
4570
}
4671

4772
o.Selector, err = labels.Parse(selector)
4873
if err != nil {
49-
return fmt.Errorf("could not parse selector %s", err.Error())
74+
return fmt.Errorf("could not parse selector %w", err)
5075
}
5176

52-
// TODO - process list and output formatting here?
53-
54-
if err := scorecard.RunTests(o); err != nil {
55-
log.Fatal(err)
77+
var scorecardOutput v1alpha2.ScorecardOutput
78+
if list {
79+
scorecardOutput, err = o.ListTests()
80+
if err != nil {
81+
return fmt.Errorf("error listing tests %w", err)
82+
}
83+
} else {
84+
scorecardOutput, err = o.RunTests()
85+
if err != nil {
86+
return fmt.Errorf("error running tests %w", err)
87+
}
5688
}
57-
return nil
89+
90+
return printOutput(outputFormat, scorecardOutput)
5891
},
5992
}
6093

6194
scorecardCmd.Flags().StringVarP(&config, "config", "c", "",
6295
"path to a new to be defined DSL yaml formatted file that configures what tests get executed")
63-
// scorecardCmd.Flags().StringVar(&bundle, "bundle", "", "path to the operator bundle contents on disk")
96+
scorecardCmd.Flags().StringVar(&kubeconfig, "kubeconfig", "", "kubeconfig path")
97+
98+
scorecardCmd.Flags().StringVar(&bundle, "bundle", "", "path to the operator bundle contents on disk")
6499
scorecardCmd.Flags().StringVarP(&selector, "selector", "l", "", "label selector to determine which tests are run")
65-
// scorecardCmd.Flags().BoolVarP(&list, "list", "L", false, "option to enable listing which tests are run")
100+
scorecardCmd.Flags().StringVarP(&namespace, "namespace", "n", "default", "namespace to run the test images in")
101+
scorecardCmd.Flags().StringVarP(&outputFormat, "output", "o", "text",
102+
"Output format for results. Valid values: text, json")
103+
scorecardCmd.Flags().StringVarP(&serviceAccount, "service-account", "s", "default", "Service account to use for tests")
104+
scorecardCmd.Flags().BoolVarP(&list, "list", "L", false, "Option to enable listing which tests are run")
105+
scorecardCmd.Flags().BoolVarP(&skipCleanup, "skip-cleanup", "x", false, "Disable resource cleanup after tests are run")
106+
scorecardCmd.Flags().DurationVarP(&waitTime, "wait-time", "w", time.Duration(30*time.Second),
107+
"seconds to wait for tests to complete. Example: 35s")
66108

67109
return scorecardCmd
68110
}
111+
112+
func printOutput(outputFormat string, output v1alpha2.ScorecardOutput) error {
113+
switch outputFormat {
114+
case "text":
115+
o, err := output.MarshalText()
116+
if err != nil {
117+
fmt.Println(err.Error())
118+
return err
119+
}
120+
fmt.Printf("%s\n", o)
121+
case "json":
122+
bytes, err := json.MarshalIndent(output, "", " ")
123+
if err != nil {
124+
fmt.Println(err.Error())
125+
return err
126+
}
127+
fmt.Printf("%s\n", string(bytes))
128+
default:
129+
return fmt.Errorf("invalid output format selected")
130+
}
131+
return nil
132+
133+
}

hack/image/build-scorecard-test-image.sh

+8-8
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ set -eux
44

55
source hack/lib/image_lib.sh
66

7-
# TODO build test image
8-
#WD="$(dirname "$(pwd)")"
9-
#GOOS=linux CGO_ENABLED=0 \
10-
# go build \
11-
# -gcflags "all=-trimpath=${WD}" \
12-
# -asmflags "all=-trimpath=${WD}" \
13-
# -o images/scorecard-test/scorecard-test \
14-
# images/scorecard-test/cmd/test/main.go
7+
# build scorecard test image
8+
WD="$(dirname "$(pwd)")"
9+
GOOS=linux CGO_ENABLED=0 \
10+
go build \
11+
-gcflags "all=-trimpath=${WD}" \
12+
-asmflags "all=-trimpath=${WD}" \
13+
-o images/scorecard-test/scorecard-test \
14+
images/scorecard-test/cmd/test/main.go
1515

1616
# Build base image
1717
pushd images/scorecard-test

images/scorecard-test/Dockerfile

+2-3
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@ ENV TEST=/usr/local/bin/scorecard-test \
55
USER_UID=1001 \
66
USER_NAME=test
77

8-
# TODO install test binary
9-
# COPY scorecard-test ${TEST}
8+
# install test binary
9+
COPY scorecard-test ${TEST}
1010

1111
COPY bin /usr/local/bin
1212
RUN /usr/local/bin/user_setup
1313

14-
1514
ENTRYPOINT ["/usr/local/bin/entrypoint"]
1615

1716
USER ${USER_UID}

images/scorecard-test/cmd/test/main.go

+42-13
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,14 @@ package main
1717
import (
1818
"encoding/json"
1919
"fmt"
20+
"io/ioutil"
2021
"log"
2122
"os"
2223

24+
"github.com/operator-framework/operator-sdk/pkg/apis/scorecard/v1alpha2"
2325
scapiv1alpha2 "github.com/operator-framework/operator-sdk/pkg/apis/scorecard/v1alpha2"
2426

27+
"github.com/operator-framework/operator-sdk/internal/scorecard/alpha"
2528
"github.com/operator-framework/operator-sdk/internal/scorecard/alpha/tests"
2629
)
2730

@@ -35,7 +38,8 @@ import (
3538
// test image.
3639

3740
const (
38-
bundlePath = "/scorecard"
41+
// bundleTar is the tar file containing the bundle contents
42+
bundleTar = "/scorecard/bundle.tar"
3943
)
4044

4145
func main() {
@@ -44,7 +48,19 @@ func main() {
4448
log.Fatal("test name argument is required")
4549
}
4650

47-
cfg, err := tests.GetBundle(bundlePath)
51+
// Create tmp directory for the untar'd bundle
52+
tmpDir, err := ioutil.TempDir("/tmp", "scorecard-bundle")
53+
if err != nil {
54+
log.Fatal(err)
55+
}
56+
defer os.Remove(tmpDir)
57+
58+
err = alpha.UntarFile(bundleTar, tmpDir)
59+
if err != nil {
60+
log.Fatalf("error untarring bundle %s", err.Error())
61+
}
62+
63+
cfg, err := tests.GetBundle(tmpDir)
4864
if err != nil {
4965
log.Fatal(err.Error())
5066
}
@@ -53,23 +69,19 @@ func main() {
5369

5470
switch entrypoint[0] {
5571
case tests.OLMBundleValidationTest:
56-
result = tests.BundleValidationTest(cfg)
72+
result = tests.BundleValidationTest(*cfg)
5773
case tests.OLMCRDsHaveValidationTest:
58-
result = tests.CRDsHaveValidationTest(cfg)
74+
result = tests.CRDsHaveValidationTest(*cfg)
5975
case tests.OLMCRDsHaveResourcesTest:
60-
result = tests.CRDsHaveResourcesTest(cfg)
76+
result = tests.CRDsHaveResourcesTest(*cfg)
6177
case tests.OLMSpecDescriptorsTest:
62-
result = tests.SpecDescriptorsTest(cfg)
78+
result = tests.SpecDescriptorsTest(*cfg)
6379
case tests.OLMStatusDescriptorsTest:
64-
result = tests.StatusDescriptorsTest(cfg)
65-
case tests.BasicCheckStatusTest:
66-
result = tests.CheckStatusTest(cfg)
80+
result = tests.StatusDescriptorsTest(*cfg)
6781
case tests.BasicCheckSpecTest:
68-
result = tests.CheckSpecTest(cfg)
82+
result = tests.CheckSpecTest(*cfg)
6983
default:
70-
log.Fatal("invalid test name argument passed")
71-
// TODO print out full list of test names to give a hint
72-
// to the end user on what the valid tests are
84+
result = printValidTests()
7385
}
7486

7587
prettyJSON, err := json.MarshalIndent(result, "", " ")
@@ -79,3 +91,20 @@ func main() {
7991
fmt.Printf("%s\n", string(prettyJSON))
8092

8193
}
94+
95+
// printValidTests will print out full list of test names to give a hint to the end user on what the valid tests are
96+
func printValidTests() (result v1alpha2.ScorecardTestResult) {
97+
result.State = scapiv1alpha2.FailState
98+
result.Errors = make([]string, 0)
99+
result.Suggestions = make([]string, 0)
100+
101+
str := fmt.Sprintf("Valid tests for this image include: %s, %s, %s, %s, %s, %s",
102+
tests.OLMBundleValidationTest,
103+
tests.OLMCRDsHaveValidationTest,
104+
tests.OLMCRDsHaveResourcesTest,
105+
tests.OLMSpecDescriptorsTest,
106+
tests.OLMStatusDescriptorsTest,
107+
tests.BasicCheckSpecTest)
108+
result.Errors = append(result.Errors, str)
109+
return result
110+
}

internal/scorecard/alpha/bundle.go

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright 2020 The Operator-SDK Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package alpha
16+
17+
import (
18+
"fmt"
19+
"io/ioutil"
20+
"os"
21+
22+
"k8s.io/apimachinery/pkg/util/rand"
23+
)
24+
25+
// getBundleData tars up the contents of a bundle from a path, and returns that tar file in []byte
26+
func getBundleData(bundlePath string) (bundleData []byte, err error) {
27+
28+
// make sure the bundle exists on disk
29+
_, err = os.Stat(bundlePath)
30+
if os.IsNotExist(err) {
31+
return bundleData, fmt.Errorf("bundle path is not valid %w", err)
32+
}
33+
34+
tempTarFileName := fmt.Sprintf("%s%ctempBundle-%s.tar", os.TempDir(), os.PathSeparator, rand.String(4))
35+
36+
paths := []string{bundlePath}
37+
err = CreateTarFile(tempTarFileName, paths)
38+
if err != nil {
39+
return bundleData, fmt.Errorf("error creating tar of bundle %w", err)
40+
}
41+
42+
defer os.Remove(tempTarFileName)
43+
44+
var buf []byte
45+
buf, err = ioutil.ReadFile(tempTarFileName)
46+
if err != nil {
47+
return bundleData, fmt.Errorf("error reading tar of bundle %w", err)
48+
}
49+
50+
return buf, err
51+
}

internal/scorecard/alpha/config.go

+35-7
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,44 @@
1414

1515
package alpha
1616

17-
type ScorecardTest struct {
18-
Name string `yaml:"name"` // The container test name
19-
Image string `yaml:"image"` // The container image name
20-
Entrypoint string `yaml:"entrypoint,omitempty"` // An optional entrypoint passed to the test image
21-
Labels map[string]string `yaml:"labels"` // User defined labels used to filter tests
22-
Description string `yaml:"description"` // User readable test description
17+
import (
18+
"io/ioutil"
19+
20+
v1 "k8s.io/api/core/v1"
21+
"sigs.k8s.io/yaml"
22+
)
23+
24+
type Test struct {
25+
Name string `yaml:"name"` // The container test name
26+
Image string `yaml:"image"` // The container image name
27+
// An list of commands and arguments passed to the test image
28+
Entrypoint []string `yaml:"entrypoint,omitempty"`
29+
Labels map[string]string `yaml:"labels"` // User defined labels used to filter tests
30+
Description string `yaml:"description"` // User readable test description
31+
TestPod *v1.Pod `yaml:"-"` // Pod that ran the test
2332
}
2433

2534
// Config represents the set of test configurations which scorecard
2635
// would run based on user input
2736
type Config struct {
28-
Tests []ScorecardTest `yaml:"tests"`
37+
Tests []Test `yaml:"tests"`
38+
}
39+
40+
// LoadConfig will find and return the scorecard config, the config file
41+
// can be passed in via command line flag or from a bundle location or
42+
// bundle image
43+
func LoadConfig(configFilePath string) (Config, error) {
44+
c := Config{}
45+
46+
// TODO handle getting config from bundle (ondisk or image)
47+
yamlFile, err := ioutil.ReadFile(configFilePath)
48+
if err != nil {
49+
return c, err
50+
}
51+
52+
if err := yaml.Unmarshal(yamlFile, &c); err != nil {
53+
return c, err
54+
}
55+
56+
return c, nil
2957
}

internal/scorecard/alpha/config_test.go

+6-5
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,12 @@ func TestInvalidConfigPath(t *testing.T) {
3030
for _, c := range cases {
3131
t.Run(c.configPathValue, func(t *testing.T) {
3232
_, err := LoadConfig(c.configPathValue)
33-
if err != nil && c.wantError {
34-
t.Logf("Wanted error and got error : %v", err)
35-
return
36-
} else if err != nil && !c.wantError {
37-
t.Errorf("Wanted result but got error: %v", err)
33+
if err == nil && c.wantError {
34+
t.Fatalf("Wanted error but got no error")
35+
} else if err != nil {
36+
if !c.wantError {
37+
t.Fatalf("Wanted result but got error: %v", err)
38+
}
3839
return
3940
}
4041

0 commit comments

Comments
 (0)