15
15
package cmdtest
16
16
17
17
import (
18
+ "bytes"
18
19
"fmt"
19
20
"io/ioutil"
20
21
"os"
@@ -27,8 +28,13 @@ import (
27
28
"github.com/operator-framework/operator-sdk/pkg/scaffold"
28
29
"github.com/operator-framework/operator-sdk/pkg/test"
29
30
31
+ "github.com/ghodss/yaml"
30
32
log "github.com/sirupsen/logrus"
31
33
"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"
32
38
)
33
39
34
40
var deployTestDir = filepath .Join (scaffold .DeployDir , "test" )
@@ -39,6 +45,7 @@ type testLocalConfig struct {
39
45
namespacedManPath string
40
46
goTestFlags string
41
47
namespace string
48
+ image string
42
49
}
43
50
44
51
var tlConfig testLocalConfig
@@ -54,6 +61,7 @@ func NewTestLocalCmd() *cobra.Command {
54
61
testCmd .Flags ().StringVar (& tlConfig .namespacedManPath , "namespaced-manifest" , "" , "Path to manifest for per-test, namespaced resources (e.g. RBAC and Operator manifest)" )
55
62
testCmd .Flags ().StringVar (& tlConfig .goTestFlags , "go-test-flags" , "" , "Additional flags to pass to go test" )
56
63
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 image from the one specified in the namespaced manifest" )
57
65
58
66
return testCmd
59
67
}
@@ -98,12 +106,14 @@ func testLocalFunc(cmd *cobra.Command, args []string) {
98
106
if err != nil {
99
107
log .Fatalf ("could not create temporary namespaced manifest file: (%v)" , err )
100
108
}
101
- defer func () {
102
- err := os .Remove (tlConfig .namespacedManPath )
103
- if err != nil {
104
- log .Fatalf ("could not delete temporary namespace manifest file: (%v)" , err )
105
- }
106
- }()
109
+ /*
110
+ defer func() {
111
+ err := os.Remove(tlConfig.namespacedManPath)
112
+ if err != nil {
113
+ log.Fatalf("could not delete temporary namespace manifest file: (%v)", err)
114
+ }
115
+ }()
116
+ */
107
117
}
108
118
if tlConfig .globalManPath == "" {
109
119
err := os .MkdirAll (deployTestDir , os .FileMode (fileutil .DefaultDirFileMode ))
@@ -141,6 +151,11 @@ func testLocalFunc(cmd *cobra.Command, args []string) {
141
151
}
142
152
}()
143
153
}
154
+ if tlConfig .image != "" {
155
+ if err := replaceImage (tlConfig .namespacedManPath , tlConfig .image ); err != nil {
156
+ log .Fatalf ("replaceImage function failed: %v" , err )
157
+ }
158
+ }
144
159
testArgs := []string {"test" , args [0 ] + "/..." }
145
160
testArgs = append (testArgs , "-" + test .KubeConfigFlag , tlConfig .kubeconfig )
146
161
testArgs = append (testArgs , "-" + test .NamespacedManPathFlag , tlConfig .namespacedManPath )
@@ -176,3 +191,69 @@ func combineManifests(base, manifest []byte) []byte {
176
191
}
177
192
return base
178
193
}
194
+
195
+ // TODO: add support for multiple deployments and containers (user would have to
196
+ // provide extra information in that case)
197
+
198
+ // replaceImage searches for a deployment and replaces the image in the container
199
+ // to the one specified in the function call. The function will fail if the
200
+ // number of deployments is not equal to one or if the deployment has multiple
201
+ // containers
202
+ func replaceImage (manifestPath , image string ) error {
203
+ yamlFile , err := ioutil .ReadFile (manifestPath )
204
+ if err != nil {
205
+ return err
206
+ }
207
+ foundDeployment := false
208
+ newManifest := []byte {}
209
+ yamlSplit := bytes .Split (yamlFile , []byte ("\n ---\n " ))
210
+ for _ , yamlSpec := range yamlSplit {
211
+ if string (yamlSpec ) == "" {
212
+ continue
213
+ }
214
+ decoded := make (map [string ]interface {})
215
+ err = yaml .Unmarshal (yamlSpec , & decoded )
216
+ if err != nil {
217
+ return err
218
+ }
219
+ kind , ok := decoded ["kind" ].(string )
220
+ if ! ok {
221
+ newManifest = combineManifests (newManifest , yamlSpec )
222
+ continue
223
+ }
224
+ if kind != "Deployment" {
225
+ newManifest = combineManifests (newManifest , yamlSpec )
226
+ continue
227
+ }
228
+ if foundDeployment {
229
+ return fmt .Errorf ("cannot use `image` flag on namespaced manifest with more than 1 deployment" )
230
+ }
231
+ foundDeployment = true
232
+ scheme := runtime .NewScheme ()
233
+ // scheme for client go
234
+ cgoscheme .AddToScheme (scheme )
235
+ dynamicDecoder := serializer .NewCodecFactory (scheme ).UniversalDeserializer ()
236
+
237
+ obj , _ , err := dynamicDecoder .Decode (yamlSpec , nil , nil )
238
+ if err != nil {
239
+ return err
240
+ }
241
+ dep := & appsv1.Deployment {}
242
+ switch o := obj .(type ) {
243
+ case * appsv1.Deployment :
244
+ dep = o
245
+ default :
246
+ return fmt .Errorf ("error in replaceImage switch case; could not convert runtime.Object to deployment" )
247
+ }
248
+ if len (dep .Spec .Template .Spec .Containers ) != 1 {
249
+ return fmt .Errorf ("cannot use `image` flag on namespaced manifest containing more than 1 container in the operator deployment" )
250
+ }
251
+ dep .Spec .Template .Spec .Containers [0 ].Image = image
252
+ updatedYamlSpec , err := yaml .Marshal (dep )
253
+ if err != nil {
254
+ return fmt .Errorf ("failed to convert deployment object back to yaml: %v" , err )
255
+ }
256
+ newManifest = combineManifests (newManifest , updatedYamlSpec )
257
+ }
258
+ return ioutil .WriteFile (manifestPath , newManifest , fileutil .DefaultFileMode )
259
+ }
0 commit comments