Skip to content

Commit 38d9863

Browse files
authored
commands/.../test/local,pkg/test: add image flag to test local (#768)
* commands/.../test/local,pkg/test: add image flag to test local
1 parent bf56b44 commit 38d9863

File tree

7 files changed

+90
-1
lines changed

7 files changed

+90
-1
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
### Added
88

99
- A new command [`operator-sdk print-deps`](https://github.com/operator-framework/operator-sdk/blob/master/doc/sdk-cli-reference.md#print-deps) which prints Golang packages and versions expected by the current Operator SDK version. Supplying `--as-file` prints packages and versions in Gopkg.toml format. ([#772](https://github.com/operator-framework/operator-sdk/pull/772))
10+
- Add [`image`](https://github.com/operator-framework/operator-sdk/blob/master/doc/sdk-cli-reference.md#flags-9) flag to `test local` subcommand ([#768](https://github.com/operator-framework/operator-sdk/pull/768))
1011

1112
### Bug fixes
1213

Gopkg.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

commands/operator-sdk/cmd/test/local.go

+75
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package cmdtest
1616

1717
import (
18+
"bytes"
1819
"fmt"
1920
"io/ioutil"
2021
"os"
@@ -27,8 +28,13 @@ import (
2728
"github.com/operator-framework/operator-sdk/pkg/scaffold"
2829
"github.com/operator-framework/operator-sdk/pkg/test"
2930

31+
"github.com/ghodss/yaml"
3032
log "github.com/sirupsen/logrus"
3133
"github.com/spf13/cobra"
34+
appsv1 "k8s.io/api/apps/v1"
35+
"k8s.io/apimachinery/pkg/runtime"
36+
"k8s.io/apimachinery/pkg/runtime/serializer"
37+
cgoscheme "k8s.io/client-go/kubernetes/scheme"
3238
)
3339

3440
var deployTestDir = filepath.Join(scaffold.DeployDir, "test")
@@ -39,6 +45,7 @@ type testLocalConfig struct {
3945
namespacedManPath string
4046
goTestFlags string
4147
namespace string
48+
image string
4249
}
4350

4451
var tlConfig testLocalConfig
@@ -54,6 +61,7 @@ func NewTestLocalCmd() *cobra.Command {
5461
testCmd.Flags().StringVar(&tlConfig.namespacedManPath, "namespaced-manifest", "", "Path to manifest for per-test, namespaced resources (e.g. RBAC and Operator manifest)")
5562
testCmd.Flags().StringVar(&tlConfig.goTestFlags, "go-test-flags", "", "Additional flags to pass to go test")
5663
testCmd.Flags().StringVar(&tlConfig.namespace, "namespace", "", "If non-empty, single namespace to run tests in")
64+
testCmd.Flags().StringVar(&tlConfig.image, "image", "", "Use a different operator image from the one specified in the namespaced manifest")
5765

5866
return testCmd
5967
}
@@ -141,6 +149,11 @@ func testLocalFunc(cmd *cobra.Command, args []string) {
141149
}
142150
}()
143151
}
152+
if tlConfig.image != "" {
153+
if err := replaceImage(tlConfig.namespacedManPath, tlConfig.image); err != nil {
154+
log.Fatalf("failed to overwrite operator image in the namespaced manifest: %v", err)
155+
}
156+
}
144157
testArgs := []string{"test", args[0] + "/..."}
145158
testArgs = append(testArgs, "-"+test.KubeConfigFlag, tlConfig.kubeconfig)
146159
testArgs = append(testArgs, "-"+test.NamespacedManPathFlag, tlConfig.namespacedManPath)
@@ -176,3 +189,65 @@ func combineManifests(base, manifest []byte) []byte {
176189
}
177190
return base
178191
}
192+
193+
// TODO: add support for multiple deployments and containers (user would have to
194+
// provide extra information in that case)
195+
196+
// replaceImage searches for a deployment and replaces the image in the container
197+
// to the one specified in the function call. The function will fail if the
198+
// number of deployments is not equal to one or if the deployment has multiple
199+
// containers
200+
func replaceImage(manifestPath, image string) error {
201+
yamlFile, err := ioutil.ReadFile(manifestPath)
202+
if err != nil {
203+
return err
204+
}
205+
foundDeployment := false
206+
newManifest := []byte{}
207+
yamlSplit := bytes.Split(yamlFile, []byte("\n---\n"))
208+
for _, yamlSpec := range yamlSplit {
209+
if string(yamlSpec) == "" {
210+
continue
211+
}
212+
decoded := make(map[string]interface{})
213+
err = yaml.Unmarshal(yamlSpec, &decoded)
214+
if err != nil {
215+
return err
216+
}
217+
kind, ok := decoded["kind"].(string)
218+
if !ok || kind != "Deployment" {
219+
newManifest = combineManifests(newManifest, yamlSpec)
220+
continue
221+
}
222+
if foundDeployment {
223+
return fmt.Errorf("cannot use `image` flag on namespaced manifest with more than 1 deployment")
224+
}
225+
foundDeployment = true
226+
scheme := runtime.NewScheme()
227+
// scheme for client go
228+
cgoscheme.AddToScheme(scheme)
229+
dynamicDecoder := serializer.NewCodecFactory(scheme).UniversalDeserializer()
230+
231+
obj, _, err := dynamicDecoder.Decode(yamlSpec, nil, nil)
232+
if err != nil {
233+
return err
234+
}
235+
dep := &appsv1.Deployment{}
236+
switch o := obj.(type) {
237+
case *appsv1.Deployment:
238+
dep = o
239+
default:
240+
return fmt.Errorf("error in replaceImage switch case; could not convert runtime.Object to deployment")
241+
}
242+
if len(dep.Spec.Template.Spec.Containers) != 1 {
243+
return fmt.Errorf("cannot use `image` flag on namespaced manifest containing more than 1 container in the operator deployment")
244+
}
245+
dep.Spec.Template.Spec.Containers[0].Image = image
246+
updatedYamlSpec, err := yaml.Marshal(dep)
247+
if err != nil {
248+
return fmt.Errorf("failed to convert deployment object back to yaml: %v", err)
249+
}
250+
newManifest = combineManifests(newManifest, updatedYamlSpec)
251+
}
252+
return ioutil.WriteFile(manifestPath, newManifest, fileutil.DefaultFileMode)
253+
}

doc/sdk-cli-reference.md

+1
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ Runs the tests locally
257257
* `--namespaced-manifest` string - path to manifest for per-test, namespaced resources (default: combines deploy/service_account.yaml, deploy/rbac.yaml, and deploy/operator.yaml)
258258
* `--namespace` string - if non-empty, single namespace to run tests in (e.g. "operator-test") (default: "")
259259
* `--go-test-flags` string - extra arguments to pass to `go test` (e.g. -f "-v -parallel=2")
260+
* `--image` string - use a different operator image from the one specified in the namespaced manifest
260261
* `-h, --help` - help for local
261262

262263
##### Use

doc/test-framework/writing-e2e-tests.md

+7
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,13 @@ as an argument. You can use `--help` to view the other configuration options and
236236
$ operator-sdk test local ./test/e2e --go-test-flags "-v -parallel=2"
237237
```
238238

239+
If you wish to specify a different operator image than specified in your `operator.yaml` file (or a user-specified
240+
namespaced manifest file), you can use the `--image` flag:
241+
242+
```shell
243+
$ operator-sdk test local ./test/e2e --image quay.io/example/my-operator:v0.0.2
244+
```
245+
239246
If you wish to run all the tests in 1 namespace (which also forces `-parallel=1`), you can use the `--namespace` flag:
240247

241248
```shell

hack/tests/test-subcommand.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ cd test/test-framework
55
# test framework with defaults
66
operator-sdk test local .
77
# test operator-sdk test flags
8-
operator-sdk test local . --global-manifest deploy/crds/cache_v1alpha1_memcached_crd.yaml --namespaced-manifest deploy/namespace-init.yaml --go-test-flags "-parallel 1" --kubeconfig $HOME/.kube/config
8+
operator-sdk test local . --global-manifest deploy/crds/cache_v1alpha1_memcached_crd.yaml --namespaced-manifest deploy/namespace-init.yaml --go-test-flags "-parallel 1" --kubeconfig $HOME/.kube/config --image=quay.io/coreos/operator-sdk-dev:test-framework-operator-runtime
99
# test operator-sdk test local single namespace mode
1010
kubectl create namespace test-memcached
1111
operator-sdk test local . --namespace=test-memcached

pkg/test/resource_creator.go

+4
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ func (ctx *TestCtx) createFromYAML(yamlFile []byte, skipIfExists bool, cleanupOp
6666
}
6767
yamlSplit := bytes.Split(yamlFile, []byte("\n---\n"))
6868
for _, yamlSpec := range yamlSplit {
69+
// some autogenerated files may include an extra `---` at the end of the file
70+
if string(yamlSpec) == "" {
71+
continue
72+
}
6973
yamlSpec, err = setNamespaceYAML(yamlSpec, namespace)
7074
if err != nil {
7175
return err

0 commit comments

Comments
 (0)