From 6dcba8727c3904f65038c81bea78803dd0b6f0e3 Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Fri, 19 Mar 2021 15:09:34 -0400 Subject: [PATCH 01/13] Update go.mod to support controller-runtime 0.6.3 Signed-off-by: Angel Misevski --- go.mod | 11 ++++++++--- go.sum | 57 +++++++++++++++++++++++++-------------------------------- 2 files changed, 33 insertions(+), 35 deletions(-) diff --git a/go.mod b/go.mod index e99155fe..5c11b1c4 100644 --- a/go.mod +++ b/go.mod @@ -7,18 +7,23 @@ require ( github.com/fatih/color v1.7.0 github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 github.com/gobwas/glob v0.2.3 + github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 // indirect github.com/google/go-cmp v0.4.0 github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 github.com/mattn/go-colorable v0.1.2 // indirect github.com/mattn/go-isatty v0.0.12 // indirect - github.com/openshift/api v0.0.0-20200930075302-db52bc4ef99f + github.com/openshift/api v3.9.0+incompatible github.com/pkg/errors v0.9.1 github.com/spf13/afero v1.2.2 github.com/stretchr/testify v1.6.1 github.com/xeipuuv/gojsonschema v1.2.0 - k8s.io/api v0.19.0 - k8s.io/apimachinery v0.19.0 + golang.org/x/net v0.0.0-20200707034311-ab3426394381 // indirect + golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4 // indirect + google.golang.org/protobuf v1.24.0 // indirect + k8s.io/api v0.18.6 + k8s.io/apimachinery v0.18.6 k8s.io/klog v1.0.0 + sigs.k8s.io/controller-runtime v0.6.3 sigs.k8s.io/yaml v1.2.0 ) diff --git a/go.sum b/go.sum index 751a995c..43399c09 100644 --- a/go.sum +++ b/go.sum @@ -24,6 +24,7 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5 github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= @@ -61,6 +62,7 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -76,9 +78,9 @@ github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0 github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.2.0 h1:QvGt2nLcHH0WK9orKa+ppBPAxREcH364nPUedEpK0TY= -github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/zapr v0.1.0 h1:h+WVe9j6HAA01niTJPA/kKH0i7e0rLZBCwauQFcRE54= github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= @@ -133,6 +135,7 @@ github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 h1:5ZkaAPbicIKTF2I64qf5Fh8Aa83Q/dnOafMYV0OMwjA= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -141,7 +144,6 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -166,14 +168,13 @@ github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk= github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= -github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I= -github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= @@ -188,11 +189,13 @@ github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= @@ -210,8 +213,6 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5 h1:hyz3dwM5QLc1Rfoz4FuWJQG5BN7tc6K1MndAUnGpQr4= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= @@ -234,6 +235,7 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -263,9 +265,8 @@ github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/openshift/api v0.0.0-20200930075302-db52bc4ef99f h1:/msM59v15x4DaAZeJnQwkVsCGTEa1mx+nSSMehZVAHs= -github.com/openshift/api v0.0.0-20200930075302-db52bc4ef99f/go.mod h1:Si/I9UGeRR3qzg01YWPmtlr0GeGk2fnuggXJRmjAZ6U= -github.com/openshift/build-machinery-go v0.0.0-20200819073603-48aa266c95f7/go.mod h1:b1BuldmJlbA/xYtdZvKi+7j5YGB44qJUJDZ9zwiNCfE= +github.com/openshift/api v3.9.0+incompatible h1:fJ/KsefYuZAjmrr3+5U9yZIZbTOpVkDDLDLFresAeYs= +github.com/openshift/api v3.9.0+incompatible/go.mod h1:dh9o4Fs58gpFXGSYfnVxGR9PnV53I8TW84pQaJDdGiY= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= @@ -279,14 +280,18 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.11 h1:DhHlBtkHWPYi8O2y31JkK0TF+DGM+51OopZjH/Ia5qI= github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= @@ -330,7 +335,6 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= @@ -338,8 +342,11 @@ go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qL go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -348,17 +355,15 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -376,14 +381,13 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -424,6 +428,7 @@ golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -438,16 +443,15 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0= gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -501,35 +505,26 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.18.6 h1:osqrAXbOQjkKIWDTjrqxWQ3w0GkKb1KA1XkUGHHYpeE= k8s.io/api v0.18.6/go.mod h1:eeyxr+cwCjMdLAmr2W3RyDI0VvTawSg/3RFFBEnmZGI= -k8s.io/api v0.19.0 h1:XyrFIJqTYZJ2DU7FBE/bSPz7b1HvbVBuBf07oeo6eTc= -k8s.io/api v0.19.0/go.mod h1:I1K45XlvTrDjmj5LoM5LuP/KYrhWbjUKT/SoPG0qTjw= k8s.io/apiextensions-apiserver v0.18.6 h1:vDlk7cyFsDyfwn2rNAO2DbmUbvXy5yT5GE3rrqOzaMo= k8s.io/apiextensions-apiserver v0.18.6/go.mod h1:lv89S7fUysXjLZO7ke783xOwVTm6lKizADfvUM/SS/M= k8s.io/apimachinery v0.18.6 h1:RtFHnfGNfd1N0LeSrKCUznz5xtUP1elRGvHJbL3Ntag= k8s.io/apimachinery v0.18.6/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= -k8s.io/apimachinery v0.19.0 h1:gjKnAda/HZp5k4xQYjL0K/Yb66IvNqjthCb03QlKpaQ= -k8s.io/apimachinery v0.19.0/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= k8s.io/apiserver v0.18.6/go.mod h1:Zt2XvTHuaZjBz6EFYzpp+X4hTmgWGy8AthNVnTdm3Wg= +k8s.io/client-go v0.18.6 h1:I+oWqJbibLSGsZj8Xs8F0aWVXJVIoUHWaaJV3kUN/Zw= k8s.io/client-go v0.18.6/go.mod h1:/fwtGLjYMS1MaM5oi+eXhKwG+1UHidUEXRh6cNsdO0Q= k8s.io/code-generator v0.18.6/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= -k8s.io/code-generator v0.19.0/go.mod h1:moqLn7w0t9cMs4+5CQyxnfA/HV8MF6aAVENF+WZZhgk= k8s.io/component-base v0.18.6/go.mod h1:knSVsibPR5K6EW2XOjEHik6sdU5nCvKMrzMt2D4In14= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200114144118-36b2048a9120 h1:RPscN6KhmG54S33L+lr3GS+oD1jmchIU0ll519K6FA4= k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/klog/v2 v2.0.0 h1:Foj74zO6RbjjP4hBEKjnYtjjAhGg4jNynUdYF6fJrok= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.2.0 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A= -k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 h1:Oh3Mzx5pJ+yIumsAD0MOECPVeXsVot0UkiaCGVyfGQY= k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= -k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6 h1:+WnxoVtG8TMiudHBSEtrVL1egv36TkkJm+bA8AxicmQ= -k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20200603063816-c1c6865ac451 h1:v8ud2Up6QK1lNOKFgiIVrZdMg7MpmSnvtrOieolJKoE= k8s.io/utils v0.0.0-20200603063816-c1c6865ac451/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= @@ -539,8 +534,6 @@ sigs.k8s.io/controller-runtime v0.6.3/go.mod h1:WlZNXcM0++oyaQt4B7C2lEE5JYRs8vJU sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E= sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= -sigs.k8s.io/structured-merge-diff/v4 v4.0.1 h1:YXTMot5Qz/X1iBRJhAt+vI+HVttY0WkSqqhKxQ0xVbA= -sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= From 6b380a931ad5a4aa3b7dd0835030d3c7c36609bc Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Fri, 19 Mar 2021 14:33:58 -0400 Subject: [PATCH 02/13] Initial port of DevWorkspaceOperator flatten library Port code from DevWorkspace Operator as of commit 95cb199a135518aa9909c8868b1342b85c509523. This version of the flattening library is designed to work with devworkspaces and the operator and needs to be adapted. Signed-off-by: Angel Misevski --- pkg/flatten/annotate.go | 39 ++++ pkg/flatten/common.go | 27 +++ pkg/flatten/flatten.go | 200 ++++++++++++++++++ pkg/flatten/helper.go | 70 ++++++ pkg/flatten/internal/testutil/common.go | 111 ++++++++++ pkg/flatten/internal/testutil/http.go | 62 ++++++ .../internal/testutil/internalRegistry.go | 41 ++++ pkg/flatten/internal/testutil/k8sClient.go | 50 +++++ pkg/flatten/network/devfile.go | 39 ++++ pkg/flatten/network/fetch.go | 56 +++++ 10 files changed, 695 insertions(+) create mode 100644 pkg/flatten/annotate.go create mode 100644 pkg/flatten/common.go create mode 100644 pkg/flatten/flatten.go create mode 100644 pkg/flatten/helper.go create mode 100644 pkg/flatten/internal/testutil/common.go create mode 100644 pkg/flatten/internal/testutil/http.go create mode 100644 pkg/flatten/internal/testutil/internalRegistry.go create mode 100644 pkg/flatten/internal/testutil/k8sClient.go create mode 100644 pkg/flatten/network/devfile.go create mode 100644 pkg/flatten/network/fetch.go diff --git a/pkg/flatten/annotate.go b/pkg/flatten/annotate.go new file mode 100644 index 00000000..0def15ea --- /dev/null +++ b/pkg/flatten/annotate.go @@ -0,0 +1,39 @@ +package flatten + +import ( + dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + "github.com/devfile/api/v2/pkg/attributes" +) + +const ( + ImportSourceAttribute = "library.devfile.io/imported-by" +) + +// AddSourceAttributesForPlugin adds an attribute 'library.devfile.io/imported-by=' to all elements of +// a plugin that support attributes. +func AddSourceAttributesForPlugin(sourceID string, plugin *dw.DevWorkspaceTemplateSpec) { + for idx, component := range plugin.Components { + if component.Attributes == nil { + plugin.Components[idx].Attributes = attributes.Attributes{} + } + plugin.Components[idx].Attributes.PutString(ImportSourceAttribute, sourceID) + } + for idx, command := range plugin.Commands { + if command.Attributes == nil { + plugin.Commands[idx].Attributes = attributes.Attributes{} + } + plugin.Commands[idx].Attributes.PutString(ImportSourceAttribute, sourceID) + } + for idx, project := range plugin.Projects { + if project.Attributes == nil { + plugin.Projects[idx].Attributes = attributes.Attributes{} + } + plugin.Projects[idx].Attributes.PutString(ImportSourceAttribute, sourceID) + } + for idx, project := range plugin.StarterProjects { + if project.Attributes == nil { + plugin.Projects[idx].Attributes = attributes.Attributes{} + } + plugin.Projects[idx].Attributes.PutString(ImportSourceAttribute, sourceID) + } +} diff --git a/pkg/flatten/common.go b/pkg/flatten/common.go new file mode 100644 index 00000000..7daddb13 --- /dev/null +++ b/pkg/flatten/common.go @@ -0,0 +1,27 @@ +// +// Copyright (c) 2019-2021 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation +// + +package flatten + +import devfile "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + +func DevWorkspaceIsFlattened(devworkspace *devfile.DevWorkspaceTemplateSpec) bool { + if devworkspace.Parent != nil { + return false + } + for _, component := range devworkspace.Components { + if component.Plugin != nil { + return false + } + } + return true +} diff --git a/pkg/flatten/flatten.go b/pkg/flatten/flatten.go new file mode 100644 index 00000000..1d506cb9 --- /dev/null +++ b/pkg/flatten/flatten.go @@ -0,0 +1,200 @@ +// +// Copyright (c) 2019-2021 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation +// + +package flatten + +import ( + "context" + "fmt" + "net/url" + "path" + + devfile "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + "github.com/devfile/api/v2/pkg/utils/overriding" + "github.com/devfile/library/pkg/flatten/network" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// ResolverTools contains required structs and data for resolving remote components of a devfile (plugins and parents) +type ResolverTools struct { + // DefaultNamespace is the default namespace to use for resolving Kubernetes ImportReferences that do not include one + DefaultNamespace string + // Context is the context used for making Kubernetes or HTTP requests + Context context.Context + // K8sClient is the Kubernetes client instance used for interacting with a cluster + K8sClient client.Client + // HttpClient is the HTTP client used for making network requests when resolving plugins or parents. + HttpClient network.HTTPGetter +} + +// ResolveDevWorkspace takes a DevWorkspaceTemplateSpec and returns a "resolved" version of it -- i.e. one where all plugins and parents +// are inlined as components. +// TODO: +// - Implement flattening for DevWorkspace parents +func ResolveDevWorkspace(workspace *devfile.DevWorkspaceTemplateSpec, tooling ResolverTools) (*devfile.DevWorkspaceTemplateSpec, error) { + resolutionCtx := &resolutionContextTree{} + resolvedDW, err := recursiveResolve(workspace, tooling, resolutionCtx) + if err != nil { + return nil, err + } + return resolvedDW, nil +} + +// recursiveResolve recursively resolves plugins and parents until the result contains no parents or plugin components. +// This is a recursive function, where resolveCtx is used to build a tree of resolved components. This is used to avoid +// plugin or parent import cycles. +func recursiveResolve(workspace *devfile.DevWorkspaceTemplateSpec, tooling ResolverTools, resolveCtx *resolutionContextTree) (*devfile.DevWorkspaceTemplateSpec, error) { + if DevWorkspaceIsFlattened(workspace) { + return workspace.DeepCopy(), nil + } + if workspace.Parent != nil { + // TODO: Add support for flattening DevWorkspace parents + return nil, fmt.Errorf("DevWorkspace parent is unsupported") + } + + resolvedContent := &devfile.DevWorkspaceTemplateSpecContent{} + resolvedContent.Projects = workspace.Projects + resolvedContent.StarterProjects = workspace.StarterProjects + resolvedContent.Commands = workspace.Commands + resolvedContent.Events = workspace.Events + + var pluginSpecContents []*devfile.DevWorkspaceTemplateSpecContent + for _, component := range workspace.Components { + if component.Plugin == nil { + // No action necessary + resolvedContent.Components = append(resolvedContent.Components, component) + } else { + pluginComponent, err := resolvePluginComponent(component.Name, component.Plugin, tooling) + if err != nil { + return nil, err + } + newCtx := resolveCtx.addPlugin(component.Name, component.Plugin) + if err := newCtx.hasCycle(); err != nil { + return nil, err + } + + resolvedPlugin, err := recursiveResolve(pluginComponent, tooling, newCtx) + if err != nil { + return nil, err + } + + AddSourceAttributesForPlugin(component.Name, resolvedPlugin) + pluginSpecContents = append(pluginSpecContents, &resolvedPlugin.DevWorkspaceTemplateSpecContent) + } + } + + resolvedContent, err := overriding.MergeDevWorkspaceTemplateSpec(resolvedContent, nil, pluginSpecContents...) + if err != nil { + return nil, fmt.Errorf("failed to merge DevWorkspace parents/plugins: %w", err) + } + + return &devfile.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: *resolvedContent, + }, nil +} + +// resolvePluginComponent resolves the DevWorkspaceTemplateSpec that a plugin component refers to. The name parameter is +// used to construct meaningful error messages (e.g. issue resolving plugin 'name') +func resolvePluginComponent( + name string, + plugin *devfile.PluginComponent, + tooling ResolverTools) (resolvedPlugin *devfile.DevWorkspaceTemplateSpec, err error) { + switch { + case plugin.Kubernetes != nil: + // Search in default namespace if namespace ref is unset + if plugin.Kubernetes.Namespace == "" { + plugin.Kubernetes.Namespace = tooling.DefaultNamespace + } + resolvedPlugin, err = resolvePluginComponentByKubernetesReference(name, plugin, tooling) + case plugin.Uri != "": + resolvedPlugin, err = resolvePluginComponentByURI(name, plugin, tooling) + case plugin.Id != "": + resolvedPlugin, err = resolvePluginComponentById(name, plugin, tooling) + default: + err = fmt.Errorf("plugin %s does not define any resources", name) + } + if err != nil { + return nil, err + } + + if plugin.Components != nil || plugin.Commands != nil { + overrideSpec, err := overriding.OverrideDevWorkspaceTemplateSpec(&resolvedPlugin.DevWorkspaceTemplateSpecContent, devfile.PluginOverrides{ + Components: plugin.Components, + Commands: plugin.Commands, + }) + + if err != nil { + return nil, err + } + resolvedPlugin.DevWorkspaceTemplateSpecContent = *overrideSpec + } + return resolvedPlugin, nil +} + +// resolvePluginComponentByKubernetesReference resolves a plugin specified by a Kubernetes reference. +// The name parameter is used to construct meaningful error messages (e.g. issue resolving plugin 'name') +func resolvePluginComponentByKubernetesReference( + name string, + plugin *devfile.PluginComponent, + tooling ResolverTools) (resolvedPlugin *devfile.DevWorkspaceTemplateSpec, err error) { + + var dwTemplate devfile.DevWorkspaceTemplate + namespacedName := types.NamespacedName{ + Name: plugin.Kubernetes.Name, + Namespace: plugin.Kubernetes.Namespace, + } + err = tooling.K8sClient.Get(tooling.Context, namespacedName, &dwTemplate) + if err != nil { + if errors.IsNotFound(err) { + return nil, fmt.Errorf("plugin for component %s not found", name) + } + return nil, fmt.Errorf("failed to retrieve plugin referenced by kubernetes name and namespace '%s': %w", name, err) + } + return &dwTemplate.Spec, nil +} + +// resolvePluginComponentById resolves a plugin specified by plugin ID and registry URL. The name parameter is used to +// construct meaningful error messages (e.g. issue resolving plugin 'name') +func resolvePluginComponentById( + name string, + plugin *devfile.PluginComponent, + tools ResolverTools) (resolvedPlugin *devfile.DevWorkspaceTemplateSpec, err error) { + + // TODO: Default registry when empty + pluginURL, err := url.Parse(plugin.RegistryUrl) + if err != nil { + return nil, fmt.Errorf("failed to parse registry URL for plugin %s: %w", name, err) + } + pluginURL.Path = path.Join(pluginURL.Path, "plugins", plugin.Id) + + dwt, err := network.FetchDevWorkspaceTemplate(pluginURL.String(), tools.HttpClient) + if err != nil { + return nil, fmt.Errorf("failed to resolve plugin %s from registry %s: %w", name, plugin.RegistryUrl, err) + } + return dwt, nil +} + +// resolvePluginComponentByURI resolves a plugin defined by URI. The name parameter is used to construct meaningful +// error messages (e.g. issue resolving plugin 'name') +func resolvePluginComponentByURI( + name string, + plugin *devfile.PluginComponent, + tools ResolverTools) (resolvedPlugin *devfile.DevWorkspaceTemplateSpec, err error) { + + dwt, err := network.FetchDevWorkspaceTemplate(plugin.Uri, tools.HttpClient) + if err != nil { + return nil, fmt.Errorf("failed to resolve plugin %s by URI: %w", name, err) + } + return dwt, nil +} diff --git a/pkg/flatten/helper.go b/pkg/flatten/helper.go new file mode 100644 index 00000000..d1301478 --- /dev/null +++ b/pkg/flatten/helper.go @@ -0,0 +1,70 @@ +// +// Copyright (c) 2019-2021 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation +// + +package flatten + +import ( + "fmt" + "reflect" + + devworkspace "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" +) + +// resolutionContextTree is a recursive structure representing information about the devfile that is +// lost when flattening (e.g. plugins, parents) +type resolutionContextTree struct { + componentName string + importReference devworkspace.ImportReference + plugins []*resolutionContextTree + parentNode *resolutionContextTree +} + +// addPlugin adds a plugin component to the resolution context. +func (t *resolutionContextTree) addPlugin(name string, plugin *devworkspace.PluginComponent) *resolutionContextTree { + newNode := &resolutionContextTree{ + componentName: name, + importReference: plugin.ImportReference, + parentNode: t, + } + t.plugins = append(t.plugins, newNode) + return newNode +} + +// hasCycle checks if the current resolutionContextTree has a cycle +func (t *resolutionContextTree) hasCycle() error { + var seenRefs []devworkspace.ImportReference + currNode := t + for currNode.parentNode != nil { + for _, seenRef := range seenRefs { + if reflect.DeepEqual(seenRef, currNode.importReference) { + return fmt.Errorf("DevWorkspace has an cycle in references: %s", formatImportCycle(t)) + } + } + seenRefs = append(seenRefs, currNode.importReference) + currNode = currNode.parentNode + } + return nil +} + +// formatImportCycle is a utility method for formatting a cycle that has been detected. Output is formatted as +// plugin1 -> plugin2 -> plugin3 -> plugin1, where pluginX are component names. +func formatImportCycle(end *resolutionContextTree) string { + cycle := fmt.Sprintf("%s", end.componentName) + for end.parentNode != nil { + end = end.parentNode + if end.parentNode == nil { + end.componentName = "devfile" + } + cycle = fmt.Sprintf("%s -> %s", end.componentName, cycle) + } + return cycle +} diff --git a/pkg/flatten/internal/testutil/common.go b/pkg/flatten/internal/testutil/common.go new file mode 100644 index 00000000..c183e658 --- /dev/null +++ b/pkg/flatten/internal/testutil/common.go @@ -0,0 +1,111 @@ +// +// Copyright (c) 2019-2021 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation +// + +package testutil + +import ( + "io/ioutil" + "path/filepath" + "strings" + "testing" + + dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + "github.com/devfile/devworkspace-operator/pkg/config" + "github.com/devfile/devfile/library/pkg/flatten/network" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/yaml" +) + +var WorkspaceTemplateDiffOpts = cmp.Options{ + cmpopts.SortSlices(func(a, b dw.Component) bool { + return strings.Compare(a.Key(), b.Key()) > 0 + }), + cmpopts.SortSlices(func(a, b string) bool { + return strings.Compare(a, b) > 0 + }), + // TODO: Devworkspace overriding results in empty []string instead of nil + cmpopts.IgnoreFields(dw.WorkspaceEvents{}, "PostStart", "PreStop", "PostStop"), +} + +var testControllerCfg = &corev1.ConfigMap{ + Data: map[string]string{ + "devworkspace.default_dockerimage.redhat-developer.web-terminal": ` +name: default-web-terminal-tooling +container: + name: default-web-terminal-tooling-container + image: test-image +`, + }, +} + +func SetupControllerCfg() { + config.SetupConfigForTesting(testControllerCfg) +} + +type TestCase struct { + Name string `json:"name"` + Input TestInput `json:"input"` + Output TestOutput `json:"output"` +} + +type TestInput struct { + Workspace dw.DevWorkspaceTemplateSpec `json:"workspace,omitempty"` + // Plugins is a map of plugin "name" to devworkspace template; namespace is ignored. + Plugins map[string]dw.DevWorkspaceTemplate `json:"plugins,omitempty"` + // DevfilePlugins is a map of plugin "name" to devfile + DevfilePlugins map[string]network.Devfile `json:"devfilePlugins,omitempty"` + // Errors is a map of plugin name to the error that should be returned when attempting to retrieve it. + Errors map[string]TestPluginError `json:"errors,omitempty"` +} + +type TestPluginError struct { + // IsNotFound marks this error as a kubernetes NotFoundError + IsNotFound bool `json:"isNotFound"` + // StatusCode defines the HTTP response code (if relevant) + StatusCode int `json:"statusCode"` + // Message is the error message returned + Message string `json:"message"` +} + +type TestOutput struct { + Workspace *dw.DevWorkspaceTemplateSpec `json:"workspace,omitempty"` + ErrRegexp *string `json:"errRegexp,omitempty"` +} + +func LoadTestCaseOrPanic(t *testing.T, testFilepath string) TestCase { + bytes, err := ioutil.ReadFile(testFilepath) + if err != nil { + t.Fatal(err) + } + var test TestCase + if err := yaml.Unmarshal(bytes, &test); err != nil { + t.Fatal(err) + } + return test +} + +func LoadAllTestsOrPanic(t *testing.T, fromDir string) []TestCase { + files, err := ioutil.ReadDir(fromDir) + if err != nil { + t.Fatal(err) + } + var tests []TestCase + for _, file := range files { + if file.IsDir() { + continue + } + tests = append(tests, LoadTestCaseOrPanic(t, filepath.Join(fromDir, file.Name()))) + } + return tests +} diff --git a/pkg/flatten/internal/testutil/http.go b/pkg/flatten/internal/testutil/http.go new file mode 100644 index 00000000..50529354 --- /dev/null +++ b/pkg/flatten/internal/testutil/http.go @@ -0,0 +1,62 @@ +// +// Copyright (c) 2019-2021 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation +// + +package testutil + +import ( + "bytes" + "errors" + "fmt" + "io" + "net/http" + + "sigs.k8s.io/yaml" + + "github.com/devfile/library/pkg/flatten/network" +) + +type FakeHTTPGetter struct { + Plugins map[string]network.Devfile + Errors map[string]TestPluginError +} + +var _ network.HTTPGetter = (*FakeHTTPGetter)(nil) + +type fakeRespBody struct { + io.Reader +} + +func (_ *fakeRespBody) Close() error { return nil } + +func (reg *FakeHTTPGetter) Get(location string) (*http.Response, error) { + if plugin, ok := reg.Plugins[location]; ok { + yamlBytes, err := yaml.Marshal(plugin) + if err != nil { + return nil, fmt.Errorf("error marshalling plugin in test: %w", err) + } + resp := &http.Response{ + StatusCode: http.StatusOK, + Body: &fakeRespBody{bytes.NewBuffer(yamlBytes)}, + } + return resp, nil + } + if err, ok := reg.Errors[location]; ok { + if err.StatusCode != 0 { + return &http.Response{ + StatusCode: err.StatusCode, + Body: &fakeRespBody{bytes.NewBuffer([]byte{})}, + }, nil + } + return nil, errors.New(err.Message) + } + return nil, fmt.Errorf("test does not define entry for plugin at URL %s", location) +} diff --git a/pkg/flatten/internal/testutil/internalRegistry.go b/pkg/flatten/internal/testutil/internalRegistry.go new file mode 100644 index 00000000..b7d868f7 --- /dev/null +++ b/pkg/flatten/internal/testutil/internalRegistry.go @@ -0,0 +1,41 @@ +// +// Copyright (c) 2019-2021 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation +// + +package testutil + +import ( + "errors" + "fmt" + + dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" +) + +type FakeInternalRegistry struct { + Plugins map[string]dw.DevWorkspaceTemplate + Errors map[string]TestPluginError +} + +func (reg *FakeInternalRegistry) IsInInternalRegistry(pluginID string) bool { + _, pluginOk := reg.Plugins[pluginID] + _, errOk := reg.Errors[pluginID] + return pluginOk || errOk +} + +func (reg *FakeInternalRegistry) ReadPluginFromInternalRegistry(pluginID string) (*dw.DevWorkspaceTemplate, error) { + if plugin, ok := reg.Plugins[pluginID]; ok { + return &plugin, nil + } + if err, ok := reg.Errors[pluginID]; ok { + return nil, errors.New(err.Message) + } + return nil, fmt.Errorf("test does not define entry for plugin %s", pluginID) +} diff --git a/pkg/flatten/internal/testutil/k8sClient.go b/pkg/flatten/internal/testutil/k8sClient.go new file mode 100644 index 00000000..1694c755 --- /dev/null +++ b/pkg/flatten/internal/testutil/k8sClient.go @@ -0,0 +1,50 @@ +// +// Copyright (c) 2019-2021 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation +// + +package testutil + +import ( + "context" + "errors" + "fmt" + + "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + k8sErrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type FakeK8sClient struct { + client.Client // To satisfy interface; override all used methods + Plugins map[string]v1alpha2.DevWorkspaceTemplate + Errors map[string]TestPluginError +} + +func (client *FakeK8sClient) Get(_ context.Context, namespacedName client.ObjectKey, obj runtime.Object) error { + template, ok := obj.(*v1alpha2.DevWorkspaceTemplate) + if !ok { + return fmt.Errorf("called Get() in fake client with non-DevWorkspaceTemplate") + } + if plugin, ok := client.Plugins[namespacedName.Name]; ok { + *template = plugin + return nil + } + if err, ok := client.Errors[namespacedName.Name]; ok { + if err.IsNotFound { + return k8sErrors.NewNotFound(schema.GroupResource{}, namespacedName.Name) + } else { + return errors.New(err.Message) + } + } + return fmt.Errorf("test does not define an entry for plugin %s", namespacedName.Name) +} diff --git a/pkg/flatten/network/devfile.go b/pkg/flatten/network/devfile.go new file mode 100644 index 00000000..044eb505 --- /dev/null +++ b/pkg/flatten/network/devfile.go @@ -0,0 +1,39 @@ +// +// Copyright (c) 2019-2021 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation +// + +package network + +import ( + "fmt" + "regexp" + + dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + devfilev2 "github.com/devfile/api/v2/pkg/devfile" +) + +var SupportedSchemaVersionRegexp = regexp.MustCompile(`^2\..+`) + +type Devfile struct { + devfilev2.DevfileHeader + dw.DevWorkspaceTemplateSpec +} + +func ConvertDevfileToDevWorkspaceTemplate(devfile *Devfile) (*dw.DevWorkspaceTemplate, error) { + if !SupportedSchemaVersionRegexp.MatchString(devfile.SchemaVersion) { + return nil, fmt.Errorf("could not process devfile: unsupported schemaVersion '%s'", devfile.SchemaVersion) + } + dwt := &dw.DevWorkspaceTemplate{} + dwt.Spec = devfile.DevWorkspaceTemplateSpec + dwt.Name = devfile.Metadata.Name // TODO: Handle additional devfile metadata once those changes are pulled in to this repo + + return dwt, nil +} diff --git a/pkg/flatten/network/fetch.go b/pkg/flatten/network/fetch.go new file mode 100644 index 00000000..4131e21b --- /dev/null +++ b/pkg/flatten/network/fetch.go @@ -0,0 +1,56 @@ +// +// Copyright (c) 2019-2021 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation +// + +package network + +import ( + "fmt" + "io/ioutil" + "net/http" + + dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + "sigs.k8s.io/yaml" +) + +type HTTPGetter interface { + Get(location string) (*http.Response, error) +} + +// FetchDevWorkspaceTemplate retreives a devfile from URL location and extracts the DevWorkspaceTemplateSpec. +func FetchDevWorkspaceTemplate(location string, httpClient HTTPGetter) (*dw.DevWorkspaceTemplateSpec, error) { + resp, err := httpClient.Get(location) + if err != nil { + return nil, fmt.Errorf("failed to fetch file from %s: %w", location, err) + } + defer resp.Body.Close() // ignoring error because what would we even do? + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("could not fetch file from %s: got status %d", location, resp.StatusCode) + } + bytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("could not read data from %s: %w", location, err) + } + + // Assume we're getting a devfile, not a DevWorkspaceTemplate (TODO: Detect type and handle both?) + devfile := &Devfile{} + err = yaml.Unmarshal(bytes, devfile) + if err != nil { + return nil, fmt.Errorf("could not unmarshal devfile from response: %w", err) + } + + dwt, err := ConvertDevfileToDevWorkspaceTemplate(devfile) + if err != nil { + return nil, fmt.Errorf("failed to convert devfile to DevWorkspaceTemplate: %s", err) + } + + return &dwt.Spec, nil +} From dedbf012ae2d8b01480c2826e7c36a7b46d10959 Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Fri, 19 Mar 2021 14:35:19 -0400 Subject: [PATCH 03/13] Port DevWorkspace Operator tests for flattening library Ports tests from DevWorkspace Operator repo as of commit 95cb199a135518aa9909c8868b1342b85c509523 Signed-off-by: Angel Misevski --- pkg/flatten/flatten_test.go | 110 ++++ pkg/flatten/internal/testutil/common.go | 34 +- .../internal/testutil/internalRegistry.go | 41 -- .../testdata/k8s-ref/already-flattened.yaml | 69 +++ .../k8s-ref/error_bad-plugin-merge.yaml | 23 + .../k8s-ref/error_conflicting-merge.yaml | 22 + .../error_error-when-retrieving-plugin.yaml | 15 + .../testdata/k8s-ref/error_has-parent.yaml | 15 + .../k8s-ref/error_plugin-not-found.yaml | 16 + .../k8s-ref/error_plugin-references-self.yml | 25 + .../k8s-ref/error_plugins-have-cycle.yml | 37 ++ .../k8s-ref/nested-plugins-annotation.yaml | 44 ++ .../testdata/k8s-ref/nodejs-workspace.yaml | 521 ++++++++++++++++++ .../k8s-ref/web-terminal-with-plugin.yaml | 82 +++ .../error_invalid-schema-version.yaml | 22 + .../testdata/plugin-id/error_on-fetch.yaml | 15 + .../plugin-id/error_plugin-not-found.yaml | 15 + .../plugin-id/error_unparseable-url.yaml | 12 + .../plugin-id/resolve-plugin-by-id.yaml | 29 + .../resolve-plugin-multiple-registries.yaml | 48 ++ .../error_invalid-schema-version.yaml | 21 + .../testdata/plugin-uri/error_on-fetch.yaml | 14 + .../plugin-uri/error_plugin-not-found.yaml | 14 + .../resolve-multiple-plugins-by-uri.yaml | 46 ++ .../plugin-uri/resolve-plugin-by-uri.yaml | 28 + 25 files changed, 1257 insertions(+), 61 deletions(-) create mode 100644 pkg/flatten/flatten_test.go delete mode 100644 pkg/flatten/internal/testutil/internalRegistry.go create mode 100644 pkg/flatten/testdata/k8s-ref/already-flattened.yaml create mode 100644 pkg/flatten/testdata/k8s-ref/error_bad-plugin-merge.yaml create mode 100644 pkg/flatten/testdata/k8s-ref/error_conflicting-merge.yaml create mode 100644 pkg/flatten/testdata/k8s-ref/error_error-when-retrieving-plugin.yaml create mode 100644 pkg/flatten/testdata/k8s-ref/error_has-parent.yaml create mode 100644 pkg/flatten/testdata/k8s-ref/error_plugin-not-found.yaml create mode 100644 pkg/flatten/testdata/k8s-ref/error_plugin-references-self.yml create mode 100644 pkg/flatten/testdata/k8s-ref/error_plugins-have-cycle.yml create mode 100644 pkg/flatten/testdata/k8s-ref/nested-plugins-annotation.yaml create mode 100644 pkg/flatten/testdata/k8s-ref/nodejs-workspace.yaml create mode 100644 pkg/flatten/testdata/k8s-ref/web-terminal-with-plugin.yaml create mode 100644 pkg/flatten/testdata/plugin-id/error_invalid-schema-version.yaml create mode 100644 pkg/flatten/testdata/plugin-id/error_on-fetch.yaml create mode 100644 pkg/flatten/testdata/plugin-id/error_plugin-not-found.yaml create mode 100644 pkg/flatten/testdata/plugin-id/error_unparseable-url.yaml create mode 100644 pkg/flatten/testdata/plugin-id/resolve-plugin-by-id.yaml create mode 100644 pkg/flatten/testdata/plugin-id/resolve-plugin-multiple-registries.yaml create mode 100644 pkg/flatten/testdata/plugin-uri/error_invalid-schema-version.yaml create mode 100644 pkg/flatten/testdata/plugin-uri/error_on-fetch.yaml create mode 100644 pkg/flatten/testdata/plugin-uri/error_plugin-not-found.yaml create mode 100644 pkg/flatten/testdata/plugin-uri/resolve-multiple-plugins-by-uri.yaml create mode 100644 pkg/flatten/testdata/plugin-uri/resolve-plugin-by-uri.yaml diff --git a/pkg/flatten/flatten_test.go b/pkg/flatten/flatten_test.go new file mode 100644 index 00000000..cd70d241 --- /dev/null +++ b/pkg/flatten/flatten_test.go @@ -0,0 +1,110 @@ +// +// Copyright (c) 2019-2021 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation +// + +package flatten + +import ( + "context" + "testing" + + "github.com/devfile/library/pkg/flatten/internal/testutil" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" +) + +func TestResolveDevWorkspaceKubernetesReference(t *testing.T) { + tests := testutil.LoadAllTestsOrPanic(t, "testdata/k8s-ref") + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + // sanity check: input defines components + assert.True(t, len(tt.Input.Workspace.Components) > 0, "Test case defines workspace with no components") + testClient := &testutil.FakeK8sClient{ + Plugins: tt.Input.Plugins, + Errors: tt.Input.Errors, + } + testResolverTools := ResolverTools{ + Context: context.Background(), + K8sClient: testClient, + } + outputWorkspace, err := ResolveDevWorkspace(&tt.Input.Workspace, testResolverTools) + if tt.Output.ErrRegexp != nil && assert.Error(t, err) { + assert.Regexp(t, *tt.Output.ErrRegexp, err.Error(), "Error message should match") + } else { + if !assert.NoError(t, err, "Should not return error") { + return + } + assert.Truef(t, cmp.Equal(tt.Output.Workspace, outputWorkspace, testutil.WorkspaceTemplateDiffOpts), + "Workspace should match expected output:\n%s", + cmp.Diff(tt.Output.Workspace, outputWorkspace, testutil.WorkspaceTemplateDiffOpts)) + } + }) + } +} + +func TestResolveDevWorkspacePluginRegistry(t *testing.T) { + tests := testutil.LoadAllTestsOrPanic(t, "testdata/plugin-id") + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + // sanity check: input defines components + assert.True(t, len(tt.Input.Workspace.Components) > 0, "Test case defines workspace with no components") + testHttpGetter := &testutil.FakeHTTPGetter{ + Plugins: tt.Input.DevfilePlugins, + Errors: tt.Input.Errors, + } + testResolverTools := ResolverTools{ + Context: context.Background(), + HttpClient: testHttpGetter, + } + outputWorkspace, err := ResolveDevWorkspace(&tt.Input.Workspace, testResolverTools) + if tt.Output.ErrRegexp != nil && assert.Error(t, err) { + assert.Regexp(t, *tt.Output.ErrRegexp, err.Error(), "Error message should match") + } else { + if !assert.NoError(t, err, "Should not return error") { + return + } + assert.Truef(t, cmp.Equal(tt.Output.Workspace, outputWorkspace, testutil.WorkspaceTemplateDiffOpts), + "Workspace should match expected output:\n%s", + cmp.Diff(tt.Output.Workspace, outputWorkspace, testutil.WorkspaceTemplateDiffOpts)) + } + }) + } +} + +func TestResolveDevWorkspacePluginURI(t *testing.T) { + tests := testutil.LoadAllTestsOrPanic(t, "testdata/plugin-uri") + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + // sanity check: input defines components + assert.True(t, len(tt.Input.Workspace.Components) > 0, "Test case defines workspace with no components") + testHttpGetter := &testutil.FakeHTTPGetter{ + Plugins: tt.Input.DevfilePlugins, + Errors: tt.Input.Errors, + } + testResolverTools := ResolverTools{ + Context: context.Background(), + HttpClient: testHttpGetter, + } + outputWorkspace, err := ResolveDevWorkspace(&tt.Input.Workspace, testResolverTools) + if tt.Output.ErrRegexp != nil && assert.Error(t, err) { + assert.Regexp(t, *tt.Output.ErrRegexp, err.Error(), "Error message should match") + } else { + if !assert.NoError(t, err, "Should not return error") { + return + } + assert.Truef(t, cmp.Equal(tt.Output.Workspace, outputWorkspace, testutil.WorkspaceTemplateDiffOpts), + "Workspace should match expected output:\n%s", + cmp.Diff(tt.Output.Workspace, outputWorkspace, testutil.WorkspaceTemplateDiffOpts)) + } + }) + } +} diff --git a/pkg/flatten/internal/testutil/common.go b/pkg/flatten/internal/testutil/common.go index c183e658..05897caf 100644 --- a/pkg/flatten/internal/testutil/common.go +++ b/pkg/flatten/internal/testutil/common.go @@ -19,14 +19,14 @@ import ( "testing" dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" - "github.com/devfile/devworkspace-operator/pkg/config" - "github.com/devfile/devfile/library/pkg/flatten/network" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - corev1 "k8s.io/api/core/v1" "sigs.k8s.io/yaml" + + "github.com/devfile/library/pkg/flatten/network" ) +// WorkspaceTemplateDiffOpts are used to compare test output against the expected result. var WorkspaceTemplateDiffOpts = cmp.Options{ cmpopts.SortSlices(func(a, b dw.Component) bool { return strings.Compare(a.Key(), b.Key()) > 0 @@ -34,31 +34,20 @@ var WorkspaceTemplateDiffOpts = cmp.Options{ cmpopts.SortSlices(func(a, b string) bool { return strings.Compare(a, b) > 0 }), - // TODO: Devworkspace overriding results in empty []string instead of nil + // TODO: Devfile overriding results in empty []string instead of nil cmpopts.IgnoreFields(dw.WorkspaceEvents{}, "PostStart", "PreStop", "PostStop"), } -var testControllerCfg = &corev1.ConfigMap{ - Data: map[string]string{ - "devworkspace.default_dockerimage.redhat-developer.web-terminal": ` -name: default-web-terminal-tooling -container: - name: default-web-terminal-tooling-container - image: test-image -`, - }, -} - -func SetupControllerCfg() { - config.SetupConfigForTesting(testControllerCfg) -} - +// TestCase describes a single test case for the library. type TestCase struct { - Name string `json:"name"` + // Name is a descriptive name of what is being tested + Name string `json:"name"` + // Input describes the test inputs Input TestInput `json:"input"` Output TestOutput `json:"output"` } +// TestInput defines the inputs required for a test case. type TestInput struct { Workspace dw.DevWorkspaceTemplateSpec `json:"workspace,omitempty"` // Plugins is a map of plugin "name" to devworkspace template; namespace is ignored. @@ -69,6 +58,7 @@ type TestInput struct { Errors map[string]TestPluginError `json:"errors,omitempty"` } +// TestPluginError describes an expected error. type TestPluginError struct { // IsNotFound marks this error as a kubernetes NotFoundError IsNotFound bool `json:"isNotFound"` @@ -78,11 +68,14 @@ type TestPluginError struct { Message string `json:"message"` } +// TestOutput describes expected test outputs. If errRegexp is not empty, it is compared to the returned error as a regular +// expression. Otherwise, the output Workspace is compared with the output of the function call. type TestOutput struct { Workspace *dw.DevWorkspaceTemplateSpec `json:"workspace,omitempty"` ErrRegexp *string `json:"errRegexp,omitempty"` } +// LoadTestCaseOrPanic loads the test file at testFilepath. func LoadTestCaseOrPanic(t *testing.T, testFilepath string) TestCase { bytes, err := ioutil.ReadFile(testFilepath) if err != nil { @@ -95,6 +88,7 @@ func LoadTestCaseOrPanic(t *testing.T, testFilepath string) TestCase { return test } +// LoadAllTestsOrPanic loads all yaml files in fromDir as test cases. func LoadAllTestsOrPanic(t *testing.T, fromDir string) []TestCase { files, err := ioutil.ReadDir(fromDir) if err != nil { diff --git a/pkg/flatten/internal/testutil/internalRegistry.go b/pkg/flatten/internal/testutil/internalRegistry.go deleted file mode 100644 index b7d868f7..00000000 --- a/pkg/flatten/internal/testutil/internalRegistry.go +++ /dev/null @@ -1,41 +0,0 @@ -// -// Copyright (c) 2019-2021 Red Hat, Inc. -// This program and the accompanying materials are made -// available under the terms of the Eclipse Public License 2.0 -// which is available at https://www.eclipse.org/legal/epl-2.0/ -// -// SPDX-License-Identifier: EPL-2.0 -// -// Contributors: -// Red Hat, Inc. - initial API and implementation -// - -package testutil - -import ( - "errors" - "fmt" - - dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" -) - -type FakeInternalRegistry struct { - Plugins map[string]dw.DevWorkspaceTemplate - Errors map[string]TestPluginError -} - -func (reg *FakeInternalRegistry) IsInInternalRegistry(pluginID string) bool { - _, pluginOk := reg.Plugins[pluginID] - _, errOk := reg.Errors[pluginID] - return pluginOk || errOk -} - -func (reg *FakeInternalRegistry) ReadPluginFromInternalRegistry(pluginID string) (*dw.DevWorkspaceTemplate, error) { - if plugin, ok := reg.Plugins[pluginID]; ok { - return &plugin, nil - } - if err, ok := reg.Errors[pluginID]; ok { - return nil, errors.New(err.Message) - } - return nil, fmt.Errorf("test does not define entry for plugin %s", pluginID) -} diff --git a/pkg/flatten/testdata/k8s-ref/already-flattened.yaml b/pkg/flatten/testdata/k8s-ref/already-flattened.yaml new file mode 100644 index 00000000..ea405d54 --- /dev/null +++ b/pkg/flatten/testdata/k8s-ref/already-flattened.yaml @@ -0,0 +1,69 @@ +name: "Already flattened workspace" + +input: + workspace: + components: + - name: dev + container: + image: quay.io/wto/web-terminal-tooling:latest + mountSources: false + memoryLimit: 256Mi + args: ["tail", "-f", "/dev/null"] + env: + - value: '\[\e[34m\]>\[\e[m\]\[\e[33m\]>\[\e[m\]' + name: PS1 + - name: web-terminal + container: + image: quay.io/eclipse/che-machine-exec:nightly + mountSources: false + command: ["/go/bin/che-machine-exec", + "--authenticated-user-id", "$(DEVWORKSPACE_CREATOR)", + "--idle-timeout", "$(DEVWORKSPACE_IDLE_TIMEOUT)", + "--pod-selector", "controller.devfile.io/devworkspace_id=$(DEVWORKSPACE_ID)", + "--use-bearer-token", + "--use-tls"] + endpoints: + - name: web-terminal + targetPort: 4444 + attributes: + protocol: http + type: ide + discoverable: "false" + secure: "true" + env: + - name: USE_BEARER_TOKEN + value: "true" + +output: + workspace: + components: + - name: dev + container: + image: quay.io/wto/web-terminal-tooling:latest + mountSources: false + memoryLimit: 256Mi + args: ["tail", "-f", "/dev/null"] + env: + - value: '\[\e[34m\]>\[\e[m\]\[\e[33m\]>\[\e[m\]' + name: PS1 + - name: web-terminal + container: + image: quay.io/eclipse/che-machine-exec:nightly + mountSources: false + command: ["/go/bin/che-machine-exec", + "--authenticated-user-id", "$(DEVWORKSPACE_CREATOR)", + "--idle-timeout", "$(DEVWORKSPACE_IDLE_TIMEOUT)", + "--pod-selector", "controller.devfile.io/devworkspace_id=$(DEVWORKSPACE_ID)", + "--use-bearer-token", + "--use-tls"] + endpoints: + - name: web-terminal + targetPort: 4444 + attributes: + protocol: http + type: ide + discoverable: "false" + secure: "true" + env: + - name: USE_BEARER_TOKEN + value: "true" diff --git a/pkg/flatten/testdata/k8s-ref/error_bad-plugin-merge.yaml b/pkg/flatten/testdata/k8s-ref/error_bad-plugin-merge.yaml new file mode 100644 index 00000000..97df12ef --- /dev/null +++ b/pkg/flatten/testdata/k8s-ref/error_bad-plugin-merge.yaml @@ -0,0 +1,23 @@ +name: "Attempting to override undefined plugin component" + +input: + workspace: + components: + - name: "bad-override" + plugin: + kubernetes: + name: override + components: + - name: non-existent + container: + memoryLimit: 512Mi + plugins: + override: + spec: + components: + - name: my-component + container: + image: test-image + +output: + errRegexp: "Some Components do not override any existing element: non-existent.*" diff --git a/pkg/flatten/testdata/k8s-ref/error_conflicting-merge.yaml b/pkg/flatten/testdata/k8s-ref/error_conflicting-merge.yaml new file mode 100644 index 00000000..a41d0ea0 --- /dev/null +++ b/pkg/flatten/testdata/k8s-ref/error_conflicting-merge.yaml @@ -0,0 +1,22 @@ +name: "Component conflicts with plugin component" + +input: + workspace: + components: + - name: "component-conflict" + plugin: + kubernetes: + name: test-plugin + - name: my-component + container: + image: test-image + plugins: + test-plugin: + spec: + components: + - name: my-component + container: + image: test-image + +output: + errRegexp: "Some Components are already defined in plugin '.*': my-component.*" diff --git a/pkg/flatten/testdata/k8s-ref/error_error-when-retrieving-plugin.yaml b/pkg/flatten/testdata/k8s-ref/error_error-when-retrieving-plugin.yaml new file mode 100644 index 00000000..ef4551bf --- /dev/null +++ b/pkg/flatten/testdata/k8s-ref/error_error-when-retrieving-plugin.yaml @@ -0,0 +1,15 @@ +name: "Error retrieving plugin" + +input: + workspace: + components: + - name: "bad-plugin" + plugin: + kubernetes: + name: test-plugin + errors: + test-plugin: + message: "Internal k8s error" + +output: + errRegexp: ".*failed to retrieve.*bad-plugin.*Internal k8s error.*" diff --git a/pkg/flatten/testdata/k8s-ref/error_has-parent.yaml b/pkg/flatten/testdata/k8s-ref/error_has-parent.yaml new file mode 100644 index 00000000..36031cee --- /dev/null +++ b/pkg/flatten/testdata/k8s-ref/error_has-parent.yaml @@ -0,0 +1,15 @@ +name: "Workspace has parent" + +input: + workspace: + parent: + kubernetes: + name: my-parent + components: + - name: my-component + container: + image: test-image + + +output: + errRegexp: "DevWorkspace parent is unsupported" diff --git a/pkg/flatten/testdata/k8s-ref/error_plugin-not-found.yaml b/pkg/flatten/testdata/k8s-ref/error_plugin-not-found.yaml new file mode 100644 index 00000000..77aef485 --- /dev/null +++ b/pkg/flatten/testdata/k8s-ref/error_plugin-not-found.yaml @@ -0,0 +1,16 @@ +name: "Referenced plugin cannot be found" + +input: + workspace: + components: + - name: "bad-plugin" + plugin: + kubernetes: + name: test-plugin + errors: + test-plugin: + isNotFound: true + message: "Plugin not found" + +output: + errRegexp: "plugin for component bad-plugin not found.*" diff --git a/pkg/flatten/testdata/k8s-ref/error_plugin-references-self.yml b/pkg/flatten/testdata/k8s-ref/error_plugin-references-self.yml new file mode 100644 index 00000000..c551e576 --- /dev/null +++ b/pkg/flatten/testdata/k8s-ref/error_plugin-references-self.yml @@ -0,0 +1,25 @@ +name: "Plugin references self" + +input: + workspace: + components: + - name: "plugin-a" + plugin: + kubernetes: + name: plugin-a + plugins: + plugin-a: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: plugin-a + spec: + components: + - name: plugin-a + plugin: + kubernetes: + name: plugin-a + namespace: devworkspace-plugins + +output: + errRegexp: "DevWorkspace has an cycle in references.*" diff --git a/pkg/flatten/testdata/k8s-ref/error_plugins-have-cycle.yml b/pkg/flatten/testdata/k8s-ref/error_plugins-have-cycle.yml new file mode 100644 index 00000000..9a8f8ee0 --- /dev/null +++ b/pkg/flatten/testdata/k8s-ref/error_plugins-have-cycle.yml @@ -0,0 +1,37 @@ +name: "Plugins have reference cycle" + +input: + workspace: + components: + - name: "plugin-a" + plugin: + kubernetes: + name: plugin-a + plugins: + plugin-a: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: plugin-a + spec: + components: + - name: plugin-b + plugin: + kubernetes: + name: plugin-b + namespace: devworkspace-plugins + plugin-b: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: plugin-b + spec: + components: + - name: plugin-a + plugin: + kubernetes: + name: plugin-a + namespace: devworkspace-plugins + +output: + errRegexp: "DevWorkspace has an cycle in references.*" diff --git a/pkg/flatten/testdata/k8s-ref/nested-plugins-annotation.yaml b/pkg/flatten/testdata/k8s-ref/nested-plugins-annotation.yaml new file mode 100644 index 00000000..37d4964c --- /dev/null +++ b/pkg/flatten/testdata/k8s-ref/nested-plugins-annotation.yaml @@ -0,0 +1,44 @@ +name: "DevWorkspace annotates nested plugins with the first plugin" + +input: + workspace: + components: + - name: root-plugin + plugin: + kubernetes: + name: test-plugin-a + namespace: test-ns + plugins: + test-plugin-a: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: plugin-a + spec: + components: + - name: plugin-b + plugin: + kubernetes: + name: test-plugin-b + namespace: test-ns + test-plugin-b: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: plugin-b + spec: + components: + - name: plugin-b-container + container: + name: test-container + image: test-img + +output: + workspace: + components: + - name: plugin-b-container + attributes: + library.devfile.io/imported-by: "root-plugin" + container: + name: test-container + image: test-img diff --git a/pkg/flatten/testdata/k8s-ref/nodejs-workspace.yaml b/pkg/flatten/testdata/k8s-ref/nodejs-workspace.yaml new file mode 100644 index 00000000..5fa2e833 --- /dev/null +++ b/pkg/flatten/testdata/k8s-ref/nodejs-workspace.yaml @@ -0,0 +1,521 @@ +name: "Theia and NodeJS plugin workspace" + +input: + workspace: + projects: + - name: web-nodejs-sample + git: + remotes: + origin: "https://github.com/che-samples/web-nodejs-sample.git" + components: + - name: che-theia + plugin: + kubernetes: + name: theia-next + namespace: devworkspace-plugins + - name: machine-exec + plugin: + kubernetes: + name: machine-exec + namespace: devworkspace-plugins + - name: typescript + plugin: + kubernetes: + name: vscode-typescript + namespace: devworkspace-plugins + components: + - name: sidecar-typescript + container: + memoryLimit: 512Mi + - name: nodejs + container: + image: quay.io/eclipse/che-nodejs10-ubi:nightly + memoryLimit: 512Mi + endpoints: + - name: nodejs + protocol: http + targetPort: 3000 + mountSources: true + commands: + - id: download-dependencies + exec: + component: nodejs + commandLine: npm install + workingDir: ${PROJECTS_ROOT}/project/app + - id: run-the-app + exec: + component: nodejs + commandLine: nodemon app.js + workingDir: ${PROJECTS_ROOT}/project/app + - id: run-the-app-with-debugging-enabled + exec: + component: nodejs + commandLine: nodemon --inspect app.js + workingDir: ${PROJECTS_ROOT}/project/app + - id: stop-the-app + exec: + component: nodejs + commandLine: >- + node_server_pids=$(pgrep -fx '.*nodemon (--inspect )?app.js' | tr "\\n" " ") && + echo "Stopping node server with PIDs: ${node_server_pids}" && + kill -15 ${node_server_pids} &>/dev/null && echo 'Done.' + - id: attach-remote-debugger + vscodeLaunch: + inlined: | + { + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "attach", + "name": "Attach to Remote", + "address": "localhost", + "port": 9229, + "localRoot": "${workspaceFolder}", + "remoteRoot": "${workspaceFolder}" + } + ] + } + plugins: + theia-next: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: theia-next + labels: + "devworkspace.devfile.io/editor-name": "che-theia" + spec: + components: + - name: plugins + volume: {} + - name: remote-endpoint + volume: {} # TODO: Fix this once ephemeral volumes are supported + - name: vsx-installer # Mainly reads the container objects and searches for those + # with che-theia.eclipse.org/vscode-extensions attributes to get VSX urls + # Those found in the dedicated containers components are with a sidecar, + # Those found in the che-theia container are without a sidecar. + attributes: + "app.kubernetes.io/part-of": che-theia.eclipse.org + "app.kubernetes.io/component": bootstrapper + container: + args: + - /bin/sh + - '-c' + - | + KUBE_API_ENDPOINT="https://kubernetes.default.svc/apis/workspace.devfile.io/v1alpha2/namespaces/${CHE_WORKSPACE_NAMESPACE}/devworkspaces/${CHE_WORKSPACE_NAME}" &&\ + TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) &&\ + WORKSPACE=$(curl -fsS --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt -H "Authorization: Bearer ${TOKEN}" $KUBE_API_ENDPOINT) &&\ + IFS=$'\n' &&\ + for container in $(echo $WORKSPACE | sed -e 's|[[,]\({"attributes":{"app.kubernetes.io\)|\n\1|g' | grep '"che-theia.eclipse.org/vscode-extensions":' | grep -e '^{"attributes".*'); do \ + dest=$(echo "$container" | sed 's|.*{"name":"THEIA_PLUGINS","value":"local-dir://\([^"][^"]*\)"}.*|\1|' - ) ;\ + urls=$(echo "$container" | sed 's|.*"che-theia.eclipse.org/vscode-extensions":\[\([^]][^]]*\)\]}.*|\1|' - ) ;\ + mkdir -p $dest ;\ + unset IFS &&\ + for url in $(echo $urls | sed 's/[",]/ /g' - ); do \ + echo; echo downloading $urls to $dest; curl -L $url > $dest/$(basename $url) ;\ + done \ + done \ + image: 'quay.io/samsahai/curl:latest' + volumeMounts: + - path: "/plugins" + name: plugins + - name: remote-runtime-injector + attributes: + "app.kubernetes.io/part-of": che-theia.eclipse.org + "app.kubernetes.io/component": bootstrapper + container: #### corresponds to `initContainer` definition in old meta.yaml. + image: "quay.io/eclipse/che-theia-endpoint-runtime-binary:7.20.0" + volumeMounts: + - path: "/remote-endpoint" + name: remote-endpoint + env: + - name: PLUGIN_REMOTE_ENDPOINT_EXECUTABLE + value: /remote-endpoint/plugin-remote-endpoint + - name: REMOTE_ENDPOINT_VOLUME_NAME + value: remote-endpoint + - name: theia-ide + attributes: + "app.kubernetes.io/name": che-theia.eclipse.org + "app.kubernetes.io/part-of": che.eclipse.org + "app.kubernetes.io/component": editor + + # Added by Che-theia at start when detecting, after cloning, that the extensions.json in the repo + # contains the vscode-pull-request-github vscode plugin. + "che-theia.eclipse.org/vscode-extensions": + - https://github.com/microsoft/vscode-pull-request-github/releases/download/v0.8.0/vscode-pull-request-github-0.8.0.vsix + container: + image: "quay.io/eclipse/che-theia:next" + env: + - name: THEIA_PLUGINS + value: local-dir:///plugins + - name: HOSTED_PLUGIN_HOSTNAME + value: 0.0.0.0 + - name: HOSTED_PLUGIN_PORT + value: "3130" + - name: THEIA_HOST + value: 0.0.0.0 + volumeMounts: + - path: "/plugins" + name: plugins + mountSources: true + memoryLimit: "512M" + endpoints: + - name: "theia" + exposure: public + targetPort: 3100 + secure: true + protocol: http + attributes: + type: ide + - name: "webviews" + exposure: public + targetPort: 3100 + protocol: http + secure: true + attributes: + type: webview + unique: "true" + - name: "theia-dev" + exposure: public + targetPort: 3130 + protocol: http + attributes: + type: ide-dev + - name: "theia-redir-1" + exposure: public + targetPort: 13131 + protocol: http + - name: "theia-redir-2" + exposure: public + targetPort: 13132 + protocol: http + - name: "theia-redir-3" + exposure: public + targetPort: 13133 + protocol: http + commands: + # Commands coming from plugin editor + - id: inject-theia-in-remote-sidecar + apply: + component: remote-runtime-injector + - id: copy-vsx + apply: + component: vsx-installer + events: + preStart: + - inject-theia-in-remote-sidecar + - copy-vsx + + machine-exec: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: machine-exec + labels: + "devworkspace.devfile.io/editor-compatibility": "che-theia" + spec: + components: + - name: che-machine-exec + attributes: + "app.kubernetes.io/name": che-terminal.eclipse.org + "app.kubernetes.io/part-of": che.eclipse.org + "app.kubernetes.io/component": terminal + container: + image: "quay.io/eclipse/che-machine-exec:7.20.0" + command: ['/go/bin/che-machine-exec'] + args: + - '--url' + - '0.0.0.0:4444' + - '--pod-selector' + - controller.devfile.io/devworkspace_id=$(DEVWORKSPACE_ID) + endpoints: + - name: "che-mach-exec" + exposure: public + targetPort: 4444 + protocol: ws + secure: true + attributes: + type: terminal + + vscode-typescript: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: vscode-typescript + labels: + "devworkspace.devfile.io/editor-compatibility": "che-theia" + spec: + components: + - name: sidecar-typescript + attributes: + "app.kubernetes.io/part-of": che-theia.eclipse.org + "app.kubernetes.io/component": vscode-plugin + + # Added by Che-theia at start when detecting, after cloning, that the extensions.json in the repo + # contains the typescript vscode plugin. + "che-theia.eclipse.org/vscode-extensions": + - https://download.jboss.org/jbosstools/vscode/3rdparty/ms-code.typescript/che-typescript-language-1.35.1.vsix + + container: + image: "quay.io/eclipse/che-sidecar-node:10-0cb5d78" + memoryLimit: '512Mi' + env: + - name: PLUGIN_REMOTE_ENDPOINT_EXECUTABLE + value: /remote-endpoint/plugin-remote-endpoint + - name: THEIA_PLUGINS + value: local-dir:///plugins/sidecars/vscode-typescript + volumeMounts: + - path: "/remote-endpoint" + name: remote-endpoint + - name: plugins + path: /plugins + + +output: + workspace: + projects: + - name: web-nodejs-sample + git: + remotes: + origin: "https://github.com/che-samples/web-nodejs-sample.git" + + components: + + - name: theia-ide + attributes: + "app.kubernetes.io/name": che-theia.eclipse.org + "app.kubernetes.io/part-of": che.eclipse.org + "app.kubernetes.io/component": editor + + # Added by Che-theia at start when detecting, after cloning, that the extensions.json in the repo + # contains the vscode-pull-request-github vscode plugin. + "che-theia.eclipse.org/vscode-extensions": + - https://github.com/microsoft/vscode-pull-request-github/releases/download/v0.8.0/vscode-pull-request-github-0.8.0.vsix + library.devfile.io/imported-by: "che-theia" + container: + image: "quay.io/eclipse/che-theia:next" + env: + - name: THEIA_PLUGINS + value: local-dir:///plugins + - name: HOSTED_PLUGIN_HOSTNAME + value: 0.0.0.0 + - name: HOSTED_PLUGIN_PORT + value: "3130" + - name: THEIA_HOST + value: 0.0.0.0 + volumeMounts: + - path: "/plugins" + name: plugins + mountSources: true + memoryLimit: "512M" + endpoints: + - name: "theia" + exposure: public + targetPort: 3100 + secure: true + protocol: http + attributes: + type: ide + - name: "webviews" + exposure: public + targetPort: 3100 + protocol: http + secure: true + attributes: + type: webview + unique: "true" + - name: "theia-dev" + exposure: public + targetPort: 3130 + protocol: http + attributes: + type: ide-dev + - name: "theia-redir-1" + exposure: public + targetPort: 13131 + protocol: http + - name: "theia-redir-2" + exposure: public + targetPort: 13132 + protocol: http + - name: "theia-redir-3" + exposure: public + targetPort: 13133 + protocol: http + + - name: plugins + attributes: + library.devfile.io/imported-by: "che-theia" + volume: {} + + - name: vsx-installer # Mainly reads the container objects and searches for those + # with che-theia.eclipse.org/vscode-extensions attributes to get VSX urls + # Those found in the dedicated containers components are with a sidecar, + # Those found in the che-theia container are without a sidecar. + attributes: + "app.kubernetes.io/part-of": che-theia.eclipse.org + "app.kubernetes.io/component": bootstrapper + library.devfile.io/imported-by: "che-theia" + container: + args: + - /bin/sh + - '-c' + - | + KUBE_API_ENDPOINT="https://kubernetes.default.svc/apis/workspace.devfile.io/v1alpha2/namespaces/${CHE_WORKSPACE_NAMESPACE}/devworkspaces/${CHE_WORKSPACE_NAME}" &&\ + TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) &&\ + WORKSPACE=$(curl -fsS --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt -H "Authorization: Bearer ${TOKEN}" $KUBE_API_ENDPOINT) &&\ + IFS=$'\n' &&\ + for container in $(echo $WORKSPACE | sed -e 's|[[,]\({"attributes":{"app.kubernetes.io\)|\n\1|g' | grep '"che-theia.eclipse.org/vscode-extensions":' | grep -e '^{"attributes".*'); do \ + dest=$(echo "$container" | sed 's|.*{"name":"THEIA_PLUGINS","value":"local-dir://\([^"][^"]*\)"}.*|\1|' - ) ;\ + urls=$(echo "$container" | sed 's|.*"che-theia.eclipse.org/vscode-extensions":\[\([^]][^]]*\)\]}.*|\1|' - ) ;\ + mkdir -p $dest ;\ + unset IFS &&\ + for url in $(echo $urls | sed 's/[",]/ /g' - ); do \ + echo; echo downloading $urls to $dest; curl -L $url > $dest/$(basename $url) ;\ + done \ + done \ + image: 'quay.io/samsahai/curl:latest' + volumeMounts: + - path: "/plugins" + name: plugins + + - name: remote-endpoint + attributes: + library.devfile.io/imported-by: "che-theia" + volume: {} + # ephemeral: true #### We should add it in the Devfile 2.0 spec ! Not critical to implement at start though + + - name: remote-runtime-injector + attributes: + "app.kubernetes.io/part-of": che-theia.eclipse.org + "app.kubernetes.io/component": bootstrapper + library.devfile.io/imported-by: "che-theia" + container: #### corresponds to `initContainer` definition in old meta.yaml. + image: "quay.io/eclipse/che-theia-endpoint-runtime-binary:7.20.0" + volumeMounts: + - path: "/remote-endpoint" + name: remote-endpoint + env: + - name: PLUGIN_REMOTE_ENDPOINT_EXECUTABLE + value: /remote-endpoint/plugin-remote-endpoint + - name: REMOTE_ENDPOINT_VOLUME_NAME + value: remote-endpoint + + - name: che-machine-exec + attributes: + "app.kubernetes.io/name": che-terminal.eclipse.org + "app.kubernetes.io/part-of": che.eclipse.org + "app.kubernetes.io/component": terminal + library.devfile.io/imported-by: "machine-exec" + container: + image: "quay.io/eclipse/che-machine-exec:7.20.0" + command: ['/go/bin/che-machine-exec'] + args: + - '--url' + - '0.0.0.0:4444' + - '--pod-selector' + - controller.devfile.io/devworkspace_id=$(DEVWORKSPACE_ID) + endpoints: + - name: "che-mach-exec" + exposure: public + targetPort: 4444 + protocol: ws + secure: true + attributes: + type: terminal + - name: sidecar-typescript + attributes: + "app.kubernetes.io/part-of": che-theia.eclipse.org + "app.kubernetes.io/component": vscode-plugin + + # Added by Che-theia at start when detecting, after cloning, that the extensions.json in the repo + # contains the typescript vscode plugin. + "che-theia.eclipse.org/vscode-extensions": + - https://download.jboss.org/jbosstools/vscode/3rdparty/ms-code.typescript/che-typescript-language-1.35.1.vsix + + library.devfile.io/imported-by: "typescript" + + container: + image: "quay.io/eclipse/che-sidecar-node:10-0cb5d78" + memoryLimit: '512Mi' + env: + - name: PLUGIN_REMOTE_ENDPOINT_EXECUTABLE + value: /remote-endpoint/plugin-remote-endpoint + - name: THEIA_PLUGINS + value: local-dir:///plugins/sidecars/vscode-typescript + volumeMounts: + - path: "/remote-endpoint" + name: remote-endpoint + - name: plugins + path: /plugins + + # User runtime container + - name: nodejs + container: + image: quay.io/eclipse/che-nodejs10-ubi:nightly + memoryLimit: 512Mi + endpoints: + - name: nodejs + protocol: http + targetPort: 3000 + mountSources: true + + commands: + + # Commands coming from plugin editor + - id: inject-theia-in-remote-sidecar + attributes: + library.devfile.io/imported-by: "che-theia" + apply: + component: remote-runtime-injector + - id: copy-vsx + attributes: + library.devfile.io/imported-by: "che-theia" + apply: + component: vsx-installer + + # User commands + - id: download-dependencies + exec: + component: nodejs + commandLine: npm install + workingDir: ${PROJECTS_ROOT}/project/app + - id: run-the-app + exec: + component: nodejs + commandLine: nodemon app.js + workingDir: ${PROJECTS_ROOT}/project/app + - id: run-the-app-with-debugging-enabled + exec: + component: nodejs + commandLine: nodemon --inspect app.js + workingDir: ${PROJECTS_ROOT}/project/app + - id: stop-the-app + exec: + component: nodejs + commandLine: >- + node_server_pids=$(pgrep -fx '.*nodemon (--inspect )?app.js' | tr "\\n" " ") && + echo "Stopping node server with PIDs: ${node_server_pids}" && + kill -15 ${node_server_pids} &>/dev/null && echo 'Done.' + - id: attach-remote-debugger + vscodeLaunch: + inlined: | + { + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "attach", + "name": "Attach to Remote", + "address": "localhost", + "port": 9229, + "localRoot": "${workspaceFolder}", + "remoteRoot": "${workspaceFolder}" + } + ] + } + + events: + preStart: + - inject-theia-in-remote-sidecar + - copy-vsx diff --git a/pkg/flatten/testdata/k8s-ref/web-terminal-with-plugin.yaml b/pkg/flatten/testdata/k8s-ref/web-terminal-with-plugin.yaml new file mode 100644 index 00000000..e30e74de --- /dev/null +++ b/pkg/flatten/testdata/k8s-ref/web-terminal-with-plugin.yaml @@ -0,0 +1,82 @@ +name: "Web terminal default" + +input: + workspace: + components: + - name: dev + container: + memoryLimit: "256Mi" + image: quay.io/wto/web-terminal-tooling:latest + args: ["tail", "-f", "/dev/null"] + env: + - name: PS1 + value: \[\e[34m\]>\[\e[m\]\[\e[33m\]>\[\e[m\] + - name: web-terminal + plugin: + kubernetes: + name: web-terminal + namespace: devworkspace-plugins + plugins: + web-terminal: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: web-terminal + labels: + "devworkspace.devfile.io/editor-name": "web-terminal" + spec: + components: + - name: web-terminal + container: + image: quay.io/eclipse/che-machine-exec:nightly + command: ["/go/bin/che-machine-exec", + "--authenticated-user-id", "$(DEVWORKSPACE_CREATOR)", + "--idle-timeout", "$(DEVWORKSPACE_IDLE_TIMEOUT)", + "--pod-selector", "controller.devfile.io/devworkspace_id=$(DEVWORKSPACE_ID)", + "--use-bearer-token", + "--use-tls"] + endpoints: + - name: web-terminal + targetPort: 4444 + attributes: + protocol: http + type: ide + discoverable: "false" + secure: "true" + env: + - name: USE_BEARER_TOKEN + value: "true" + +output: + workspace: + components: + - name: dev + container: + image: quay.io/wto/web-terminal-tooling:latest + memoryLimit: 256Mi + args: ["tail", "-f", "/dev/null"] + env: + - value: '\[\e[34m\]>\[\e[m\]\[\e[33m\]>\[\e[m\]' + name: PS1 + - name: web-terminal + attributes: + library.devfile.io/imported-by: "web-terminal" + container: + image: quay.io/eclipse/che-machine-exec:nightly + command: ["/go/bin/che-machine-exec", + "--authenticated-user-id", "$(DEVWORKSPACE_CREATOR)", + "--idle-timeout", "$(DEVWORKSPACE_IDLE_TIMEOUT)", + "--pod-selector", "controller.devfile.io/devworkspace_id=$(DEVWORKSPACE_ID)", + "--use-bearer-token", + "--use-tls"] + endpoints: + - name: web-terminal + targetPort: 4444 + attributes: + protocol: http + type: ide + discoverable: "false" + secure: "true" + env: + - name: USE_BEARER_TOKEN + value: "true" diff --git a/pkg/flatten/testdata/plugin-id/error_invalid-schema-version.yaml b/pkg/flatten/testdata/plugin-id/error_invalid-schema-version.yaml new file mode 100644 index 00000000..9b2839ac --- /dev/null +++ b/pkg/flatten/testdata/plugin-id/error_invalid-schema-version.yaml @@ -0,0 +1,22 @@ +name: "DevWorkspace references plugin with invalid schemaVersion" + +input: + workspace: + components: + - name: test-plugin + plugin: + id: my/test/plugin + registryUrl: "https://test-registry.io/subpath" + devfilePlugins: + "https://test-registry.io/subpath/plugins/my/test/plugin": + schemaVersion: 1.0.0 + metadata: + name: "plugin-a" + components: + - name: plugin-a + container: + name: test-container + image: test-image + +output: + errRegexp: "could not process devfile: unsupported schemaVersion '1.0.0'" diff --git a/pkg/flatten/testdata/plugin-id/error_on-fetch.yaml b/pkg/flatten/testdata/plugin-id/error_on-fetch.yaml new file mode 100644 index 00000000..92a44a69 --- /dev/null +++ b/pkg/flatten/testdata/plugin-id/error_on-fetch.yaml @@ -0,0 +1,15 @@ +name: "Error when fetching plugin" + +input: + workspace: + components: + - name: test-plugin + plugin: + id: my/test/plugin + registryUrl: "https://test-registry.io/subpath" + errors: + "https://test-registry.io/subpath/plugins/my/test/plugin": + message: "testing error" + +output: + errRegexp: "failed to fetch file from.*testing error" diff --git a/pkg/flatten/testdata/plugin-id/error_plugin-not-found.yaml b/pkg/flatten/testdata/plugin-id/error_plugin-not-found.yaml new file mode 100644 index 00000000..36da9f47 --- /dev/null +++ b/pkg/flatten/testdata/plugin-id/error_plugin-not-found.yaml @@ -0,0 +1,15 @@ +name: "Plugin not found in registry" + +input: + workspace: + components: + - name: test-plugin + plugin: + id: my/test/plugin + registryUrl: "https://test-registry.io/subpath" + errors: + "https://test-registry.io/subpath/plugins/my/test/plugin": + statusCode: 404 + +output: + errRegexp: "could not fetch file from.*got status 404" diff --git a/pkg/flatten/testdata/plugin-id/error_unparseable-url.yaml b/pkg/flatten/testdata/plugin-id/error_unparseable-url.yaml new file mode 100644 index 00000000..62fd534a --- /dev/null +++ b/pkg/flatten/testdata/plugin-id/error_unparseable-url.yaml @@ -0,0 +1,12 @@ +name: "Error when parsing registryURL" + +input: + workspace: + components: + - name: test-plugin + plugin: + id: my/test/plugin + registryUrl: ":/test-registry.io/subpath" + +output: + errRegexp: "failed to parse registry URL for plugin test-plugin" diff --git a/pkg/flatten/testdata/plugin-id/resolve-plugin-by-id.yaml b/pkg/flatten/testdata/plugin-id/resolve-plugin-by-id.yaml new file mode 100644 index 00000000..fb3cf8b6 --- /dev/null +++ b/pkg/flatten/testdata/plugin-id/resolve-plugin-by-id.yaml @@ -0,0 +1,29 @@ +name: "DevWorkspace references plugin from plugin registry" + +input: + workspace: + components: + - name: test-plugin + plugin: + id: my/test/plugin + registryUrl: "https://test-registry.io/subpath" + devfilePlugins: + "https://test-registry.io/subpath/plugins/my/test/plugin": + schemaVersion: 2.0.0 + metadata: + name: "plugin-a" + components: + - name: plugin-a + container: + name: test-container + image: test-image + +output: + workspace: + components: + - name: plugin-a + attributes: + library.devfile.io/imported-by: "test-plugin" + container: + name: test-container + image: test-image diff --git a/pkg/flatten/testdata/plugin-id/resolve-plugin-multiple-registries.yaml b/pkg/flatten/testdata/plugin-id/resolve-plugin-multiple-registries.yaml new file mode 100644 index 00000000..bdc4b18e --- /dev/null +++ b/pkg/flatten/testdata/plugin-id/resolve-plugin-multiple-registries.yaml @@ -0,0 +1,48 @@ +name: "DevWorkspace references plugins from multiple plugin registries" + +input: + workspace: + components: + - name: test-plugin + plugin: + id: my/test/plugin + registryUrl: "https://test-registry.io/subpath" + - name: test-plugin-2 + plugin: + id: my/test/plugin-2 + registryUrl: "https://test-registry-2.io/subpath" + devfilePlugins: + "https://test-registry.io/subpath/plugins/my/test/plugin": + schemaVersion: 2.0.0 + metadata: + name: "plugin-a" + components: + - name: plugin-a + container: + name: test-container + image: test-image + "https://test-registry-2.io/subpath/plugins/my/test/plugin-2": + schemaVersion: 2.0.0 + metadata: + name: "plugin-b" + components: + - name: plugin-b + container: + name: test-container-b + image: test-image + +output: + workspace: + components: + - name: plugin-a + attributes: + library.devfile.io/imported-by: "test-plugin" + container: + name: test-container + image: test-image + - name: plugin-b + attributes: + library.devfile.io/imported-by: "test-plugin-2" + container: + name: test-container-b + image: test-image diff --git a/pkg/flatten/testdata/plugin-uri/error_invalid-schema-version.yaml b/pkg/flatten/testdata/plugin-uri/error_invalid-schema-version.yaml new file mode 100644 index 00000000..ea5f777a --- /dev/null +++ b/pkg/flatten/testdata/plugin-uri/error_invalid-schema-version.yaml @@ -0,0 +1,21 @@ +name: "DevWorkspace references plugin with invalid schemaVersion" + +input: + workspace: + components: + - name: test-plugin + plugin: + uri: https://test-registry.io/old-devfiles + devfilePlugins: + "https://test-registry.io/old-devfiles": + schemaVersion: 1.0.0 + metadata: + name: "plugin-a" + components: + - name: plugin-a + container: + name: test-container + image: test-image + +output: + errRegexp: "could not process devfile: unsupported schemaVersion '1.0.0'" diff --git a/pkg/flatten/testdata/plugin-uri/error_on-fetch.yaml b/pkg/flatten/testdata/plugin-uri/error_on-fetch.yaml new file mode 100644 index 00000000..45bfd6d7 --- /dev/null +++ b/pkg/flatten/testdata/plugin-uri/error_on-fetch.yaml @@ -0,0 +1,14 @@ +name: "Error when fetching plugin" + +input: + workspace: + components: + - name: test-plugin + plugin: + uri: https://test-registry.io/error + errors: + "https://test-registry.io/error": + message: "testing error" + +output: + errRegexp: "failed to fetch file from.*testing error" diff --git a/pkg/flatten/testdata/plugin-uri/error_plugin-not-found.yaml b/pkg/flatten/testdata/plugin-uri/error_plugin-not-found.yaml new file mode 100644 index 00000000..5a1da1af --- /dev/null +++ b/pkg/flatten/testdata/plugin-uri/error_plugin-not-found.yaml @@ -0,0 +1,14 @@ +name: "Plugin not found in at URI" + +input: + workspace: + components: + - name: test-plugin + plugin: + uri: "https://test-registry.io/notfound" + errors: + "https://test-registry.io/notfound": + statusCode: 404 + +output: + errRegexp: "could not fetch file from.*got status 404" diff --git a/pkg/flatten/testdata/plugin-uri/resolve-multiple-plugins-by-uri.yaml b/pkg/flatten/testdata/plugin-uri/resolve-multiple-plugins-by-uri.yaml new file mode 100644 index 00000000..c137e363 --- /dev/null +++ b/pkg/flatten/testdata/plugin-uri/resolve-multiple-plugins-by-uri.yaml @@ -0,0 +1,46 @@ +name: "DevWorkspace references plugins from multiple plugin registries" + +input: + workspace: + components: + - name: test-plugin + plugin: + uri: "https://my-plugin.io/test" + - name: test-plugin-2 + plugin: + uri: "https://my-plugin-alt.io/test" + devfilePlugins: + "https://my-plugin.io/test": + schemaVersion: 2.0.0 + metadata: + name: "plugin-a" + components: + - name: plugin-a + container: + name: test-container + image: test-image + "https://my-plugin-alt.io/test": + schemaVersion: 2.0.0 + metadata: + name: "plugin-b" + components: + - name: plugin-b + container: + name: test-container-b + image: test-image + +output: + workspace: + components: + - name: plugin-a + attributes: + library.devfile.io/imported-by: "test-plugin" + container: + name: test-container + image: test-image + - name: plugin-b + attributes: + library.devfile.io/imported-by: "test-plugin-2" + container: + name: test-container-b + image: test-image diff --git a/pkg/flatten/testdata/plugin-uri/resolve-plugin-by-uri.yaml b/pkg/flatten/testdata/plugin-uri/resolve-plugin-by-uri.yaml new file mode 100644 index 00000000..f20c525c --- /dev/null +++ b/pkg/flatten/testdata/plugin-uri/resolve-plugin-by-uri.yaml @@ -0,0 +1,28 @@ +name: "DevWorkspace references plugin by URI" + +input: + workspace: + components: + - name: test-plugin + plugin: + uri: "https://my-plugin.io/test" + devfilePlugins: + "https://my-plugin.io/test": + schemaVersion: 2.0.0 + metadata: + name: "plugin-a" + components: + - name: plugin-a + container: + name: test-container + image: test-image + +output: + workspace: + components: + - name: plugin-a + attributes: + library.devfile.io/imported-by: "test-plugin" + container: + name: test-container + image: test-image From 237b97f7fe9cad5ea94fd927accbb1b3dee1dcfd Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Fri, 19 Mar 2021 15:26:50 -0400 Subject: [PATCH 04/13] Make resolving utility functions type agnostic Make the utility functions used for resolving plugins by ID, URI, or Kubernetes reference not depend on the PluginComponent type and instead take the relevant information directly. This is required to allow reusing these functions for resolving Devfile parents Signed-off-by: Angel Misevski --- pkg/flatten/flatten.go | 49 +++++++++++-------- .../error_invalid-schema-version.yaml | 2 +- .../testdata/plugin-id/error_on-fetch.yaml | 2 +- .../plugin-id/error_plugin-not-found.yaml | 2 +- .../plugin-id/error_unparseable-url.yaml | 2 +- .../plugin-id/resolve-plugin-by-id.yaml | 2 +- .../resolve-plugin-multiple-registries.yaml | 4 +- 7 files changed, 35 insertions(+), 28 deletions(-) diff --git a/pkg/flatten/flatten.go b/pkg/flatten/flatten.go index 1d506cb9..62c29551 100644 --- a/pkg/flatten/flatten.go +++ b/pkg/flatten/flatten.go @@ -30,6 +30,8 @@ import ( type ResolverTools struct { // DefaultNamespace is the default namespace to use for resolving Kubernetes ImportReferences that do not include one DefaultNamespace string + // DefaultRegistryURL is the default registry URL to use when a component specifies an id but not registryURL + DefaultRegistryURL string // Context is the context used for making Kubernetes or HTTP requests Context context.Context // K8sClient is the Kubernetes client instance used for interacting with a cluster @@ -116,11 +118,11 @@ func resolvePluginComponent( if plugin.Kubernetes.Namespace == "" { plugin.Kubernetes.Namespace = tooling.DefaultNamespace } - resolvedPlugin, err = resolvePluginComponentByKubernetesReference(name, plugin, tooling) + resolvedPlugin, err = resolveElementByKubernetesImport(name, plugin.Kubernetes, tooling) case plugin.Uri != "": - resolvedPlugin, err = resolvePluginComponentByURI(name, plugin, tooling) + resolvedPlugin, err = resolveElementByURI(name, plugin.Uri, tooling) case plugin.Id != "": - resolvedPlugin, err = resolvePluginComponentById(name, plugin, tooling) + resolvedPlugin, err = resolveElementById(name, plugin.Id, plugin.RegistryUrl, tooling) default: err = fmt.Errorf("plugin %s does not define any resources", name) } @@ -142,17 +144,17 @@ func resolvePluginComponent( return resolvedPlugin, nil } -// resolvePluginComponentByKubernetesReference resolves a plugin specified by a Kubernetes reference. +// resolveElementByKubernetesImport resolves a plugin specified by a Kubernetes reference. // The name parameter is used to construct meaningful error messages (e.g. issue resolving plugin 'name') -func resolvePluginComponentByKubernetesReference( +func resolveElementByKubernetesImport( name string, - plugin *devfile.PluginComponent, + kubeReference *devfile.KubernetesCustomResourceImportReference, tooling ResolverTools) (resolvedPlugin *devfile.DevWorkspaceTemplateSpec, err error) { var dwTemplate devfile.DevWorkspaceTemplate namespacedName := types.NamespacedName{ - Name: plugin.Kubernetes.Name, - Namespace: plugin.Kubernetes.Namespace, + Name: kubeReference.Name, + Namespace: kubeReference.Namespace, } err = tooling.K8sClient.Get(tooling.Context, namespacedName, &dwTemplate) if err != nil { @@ -164,37 +166,42 @@ func resolvePluginComponentByKubernetesReference( return &dwTemplate.Spec, nil } -// resolvePluginComponentById resolves a plugin specified by plugin ID and registry URL. The name parameter is used to -// construct meaningful error messages (e.g. issue resolving plugin 'name') -func resolvePluginComponentById( +// resolveElementById resolves a component specified by ID and registry URL. The name parameter is used to +// construct meaningful error messages (e.g. issue resolving plugin 'name'). When registry URL is empty, +// the DefaultRegistryURL from tools is used. +func resolveElementById( name string, - plugin *devfile.PluginComponent, + id string, + registryUrl string, tools ResolverTools) (resolvedPlugin *devfile.DevWorkspaceTemplateSpec, err error) { // TODO: Default registry when empty - pluginURL, err := url.Parse(plugin.RegistryUrl) + if registryUrl == "" { + registryUrl = tools.DefaultRegistryURL + } + pluginURL, err := url.Parse(registryUrl) if err != nil { - return nil, fmt.Errorf("failed to parse registry URL for plugin %s: %w", name, err) + return nil, fmt.Errorf("failed to parse registry URL for component %s: %w", name, err) } - pluginURL.Path = path.Join(pluginURL.Path, "plugins", plugin.Id) + pluginURL.Path = path.Join(pluginURL.Path, id) dwt, err := network.FetchDevWorkspaceTemplate(pluginURL.String(), tools.HttpClient) if err != nil { - return nil, fmt.Errorf("failed to resolve plugin %s from registry %s: %w", name, plugin.RegistryUrl, err) + return nil, fmt.Errorf("failed to resolve component %s from registry %s: %w", name, registryUrl, err) } return dwt, nil } -// resolvePluginComponentByURI resolves a plugin defined by URI. The name parameter is used to construct meaningful +// resolveElementByURI resolves a plugin defined by URI. The name parameter is used to construct meaningful // error messages (e.g. issue resolving plugin 'name') -func resolvePluginComponentByURI( +func resolveElementByURI( name string, - plugin *devfile.PluginComponent, + uri string, tools ResolverTools) (resolvedPlugin *devfile.DevWorkspaceTemplateSpec, err error) { - dwt, err := network.FetchDevWorkspaceTemplate(plugin.Uri, tools.HttpClient) + dwt, err := network.FetchDevWorkspaceTemplate(uri, tools.HttpClient) if err != nil { - return nil, fmt.Errorf("failed to resolve plugin %s by URI: %w", name, err) + return nil, fmt.Errorf("failed to resolve component %s by URI: %w", name, err) } return dwt, nil } diff --git a/pkg/flatten/testdata/plugin-id/error_invalid-schema-version.yaml b/pkg/flatten/testdata/plugin-id/error_invalid-schema-version.yaml index 9b2839ac..4eaff077 100644 --- a/pkg/flatten/testdata/plugin-id/error_invalid-schema-version.yaml +++ b/pkg/flatten/testdata/plugin-id/error_invalid-schema-version.yaml @@ -8,7 +8,7 @@ input: id: my/test/plugin registryUrl: "https://test-registry.io/subpath" devfilePlugins: - "https://test-registry.io/subpath/plugins/my/test/plugin": + "https://test-registry.io/subpath/my/test/plugin": schemaVersion: 1.0.0 metadata: name: "plugin-a" diff --git a/pkg/flatten/testdata/plugin-id/error_on-fetch.yaml b/pkg/flatten/testdata/plugin-id/error_on-fetch.yaml index 92a44a69..6267d350 100644 --- a/pkg/flatten/testdata/plugin-id/error_on-fetch.yaml +++ b/pkg/flatten/testdata/plugin-id/error_on-fetch.yaml @@ -8,7 +8,7 @@ input: id: my/test/plugin registryUrl: "https://test-registry.io/subpath" errors: - "https://test-registry.io/subpath/plugins/my/test/plugin": + "https://test-registry.io/subpath/my/test/plugin": message: "testing error" output: diff --git a/pkg/flatten/testdata/plugin-id/error_plugin-not-found.yaml b/pkg/flatten/testdata/plugin-id/error_plugin-not-found.yaml index 36da9f47..eccc3583 100644 --- a/pkg/flatten/testdata/plugin-id/error_plugin-not-found.yaml +++ b/pkg/flatten/testdata/plugin-id/error_plugin-not-found.yaml @@ -8,7 +8,7 @@ input: id: my/test/plugin registryUrl: "https://test-registry.io/subpath" errors: - "https://test-registry.io/subpath/plugins/my/test/plugin": + "https://test-registry.io/subpath/my/test/plugin": statusCode: 404 output: diff --git a/pkg/flatten/testdata/plugin-id/error_unparseable-url.yaml b/pkg/flatten/testdata/plugin-id/error_unparseable-url.yaml index 62fd534a..4669ce02 100644 --- a/pkg/flatten/testdata/plugin-id/error_unparseable-url.yaml +++ b/pkg/flatten/testdata/plugin-id/error_unparseable-url.yaml @@ -9,4 +9,4 @@ input: registryUrl: ":/test-registry.io/subpath" output: - errRegexp: "failed to parse registry URL for plugin test-plugin" + errRegexp: "failed to parse registry URL for component test-plugin" diff --git a/pkg/flatten/testdata/plugin-id/resolve-plugin-by-id.yaml b/pkg/flatten/testdata/plugin-id/resolve-plugin-by-id.yaml index fb3cf8b6..82135905 100644 --- a/pkg/flatten/testdata/plugin-id/resolve-plugin-by-id.yaml +++ b/pkg/flatten/testdata/plugin-id/resolve-plugin-by-id.yaml @@ -8,7 +8,7 @@ input: id: my/test/plugin registryUrl: "https://test-registry.io/subpath" devfilePlugins: - "https://test-registry.io/subpath/plugins/my/test/plugin": + "https://test-registry.io/subpath/my/test/plugin": schemaVersion: 2.0.0 metadata: name: "plugin-a" diff --git a/pkg/flatten/testdata/plugin-id/resolve-plugin-multiple-registries.yaml b/pkg/flatten/testdata/plugin-id/resolve-plugin-multiple-registries.yaml index bdc4b18e..ec7f4e0c 100644 --- a/pkg/flatten/testdata/plugin-id/resolve-plugin-multiple-registries.yaml +++ b/pkg/flatten/testdata/plugin-id/resolve-plugin-multiple-registries.yaml @@ -12,7 +12,7 @@ input: id: my/test/plugin-2 registryUrl: "https://test-registry-2.io/subpath" devfilePlugins: - "https://test-registry.io/subpath/plugins/my/test/plugin": + "https://test-registry.io/subpath/my/test/plugin": schemaVersion: 2.0.0 metadata: name: "plugin-a" @@ -21,7 +21,7 @@ input: container: name: test-container image: test-image - "https://test-registry-2.io/subpath/plugins/my/test/plugin-2": + "https://test-registry-2.io/subpath/my/test/plugin-2": schemaVersion: 2.0.0 metadata: name: "plugin-b" From c76d82143fa6e92bdb1b0a3c634bd5e4806202c3 Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Fri, 19 Mar 2021 16:34:07 -0400 Subject: [PATCH 05/13] Add support for resolving devfile parents in same way as plugins Add support for resolving devfile parents in same way we resolve plugins (supporting id, URI, and kubernetes reference) Signed-off-by: Angel Misevski --- pkg/flatten/flatten.go | 44 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/pkg/flatten/flatten.go b/pkg/flatten/flatten.go index 62c29551..45e37fd9 100644 --- a/pkg/flatten/flatten.go +++ b/pkg/flatten/flatten.go @@ -60,9 +60,17 @@ func recursiveResolve(workspace *devfile.DevWorkspaceTemplateSpec, tooling Resol if DevWorkspaceIsFlattened(workspace) { return workspace.DeepCopy(), nil } + resolvedParent := &devfile.DevWorkspaceTemplateSpecContent{} if workspace.Parent != nil { - // TODO: Add support for flattening DevWorkspace parents - return nil, fmt.Errorf("DevWorkspace parent is unsupported") + resolvedParentSpec, err := resolveParentComponent(workspace.Parent, tooling) + if err != nil { + return nil, err + } + if !DevWorkspaceIsFlattened(resolvedParentSpec) { + // TODO: implemenent this + return nil, fmt.Errorf("parents containing plugins or parents are not supported") + } + resolvedParent = &resolvedParentSpec.DevWorkspaceTemplateSpecContent } resolvedContent := &devfile.DevWorkspaceTemplateSpecContent{} @@ -96,7 +104,7 @@ func recursiveResolve(workspace *devfile.DevWorkspaceTemplateSpec, tooling Resol } } - resolvedContent, err := overriding.MergeDevWorkspaceTemplateSpec(resolvedContent, nil, pluginSpecContents...) + resolvedContent, err := overriding.MergeDevWorkspaceTemplateSpec(resolvedContent, resolvedParent, pluginSpecContents...) if err != nil { return nil, fmt.Errorf("failed to merge DevWorkspace parents/plugins: %w", err) } @@ -106,6 +114,36 @@ func recursiveResolve(workspace *devfile.DevWorkspaceTemplateSpec, tooling Resol }, nil } +// resolveParentComponent resolves the parent DevWorkspaceTemplateSpec that a parent reference refers to. +func resolveParentComponent(parent *devfile.Parent, tooling ResolverTools) (resolvedParent *devfile.DevWorkspaceTemplateSpec, err error) { + switch { + case parent.Kubernetes != nil: + // Search in default namespace if namespace ref is unset + if parent.Kubernetes.Namespace == "" { + parent.Kubernetes.Namespace = tooling.DefaultNamespace + } + resolvedParent, err = resolveElementByKubernetesImport("parent", parent.Kubernetes, tooling) + case parent.Uri != "": + resolvedParent, err = resolveElementByURI("parent", parent.Uri, tooling) + case parent.Id != "": + resolvedParent, err = resolveElementById("parent", parent.Id, parent.RegistryUrl, tooling) + default: + err = fmt.Errorf("devfile parent does not define any resources") + } + if err != nil { + return nil, err + } + if parent.Components != nil || parent.Commands != nil || parent.Projects != nil || parent.StarterProjects != nil { + overrideSpec, err := overriding.OverrideDevWorkspaceTemplateSpec(&resolvedParent.DevWorkspaceTemplateSpecContent, parent.ParentOverrides) + + if err != nil { + return nil, err + } + resolvedParent.DevWorkspaceTemplateSpecContent = *overrideSpec + } + return resolvedParent, nil +} + // resolvePluginComponent resolves the DevWorkspaceTemplateSpec that a plugin component refers to. The name parameter is // used to construct meaningful error messages (e.g. issue resolving plugin 'name') func resolvePluginComponent( From 0dd483026dc065fe502c24571daf897c7e353280 Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Fri, 19 Mar 2021 16:36:00 -0400 Subject: [PATCH 06/13] Clean up devfile object handling to use built-in struct Signed-off-by: Angel Misevski --- pkg/flatten/internal/testutil/common.go | 4 +--- pkg/flatten/internal/testutil/http.go | 7 ++++--- pkg/flatten/network/devfile.go | 8 +------- pkg/flatten/network/fetch.go | 2 +- 4 files changed, 7 insertions(+), 14 deletions(-) diff --git a/pkg/flatten/internal/testutil/common.go b/pkg/flatten/internal/testutil/common.go index 05897caf..289ca5d6 100644 --- a/pkg/flatten/internal/testutil/common.go +++ b/pkg/flatten/internal/testutil/common.go @@ -22,8 +22,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "sigs.k8s.io/yaml" - - "github.com/devfile/library/pkg/flatten/network" ) // WorkspaceTemplateDiffOpts are used to compare test output against the expected result. @@ -53,7 +51,7 @@ type TestInput struct { // Plugins is a map of plugin "name" to devworkspace template; namespace is ignored. Plugins map[string]dw.DevWorkspaceTemplate `json:"plugins,omitempty"` // DevfilePlugins is a map of plugin "name" to devfile - DevfilePlugins map[string]network.Devfile `json:"devfilePlugins,omitempty"` + DevfilePlugins map[string]dw.Devfile `json:"devfilePlugins,omitempty"` // Errors is a map of plugin name to the error that should be returned when attempting to retrieve it. Errors map[string]TestPluginError `json:"errors,omitempty"` } diff --git a/pkg/flatten/internal/testutil/http.go b/pkg/flatten/internal/testutil/http.go index 50529354..05276953 100644 --- a/pkg/flatten/internal/testutil/http.go +++ b/pkg/flatten/internal/testutil/http.go @@ -19,13 +19,14 @@ import ( "io" "net/http" - "sigs.k8s.io/yaml" - "github.com/devfile/library/pkg/flatten/network" + + dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + "sigs.k8s.io/yaml" ) type FakeHTTPGetter struct { - Plugins map[string]network.Devfile + Plugins map[string]dw.Devfile Errors map[string]TestPluginError } diff --git a/pkg/flatten/network/devfile.go b/pkg/flatten/network/devfile.go index 044eb505..c866fb56 100644 --- a/pkg/flatten/network/devfile.go +++ b/pkg/flatten/network/devfile.go @@ -17,17 +17,11 @@ import ( "regexp" dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" - devfilev2 "github.com/devfile/api/v2/pkg/devfile" ) var SupportedSchemaVersionRegexp = regexp.MustCompile(`^2\..+`) -type Devfile struct { - devfilev2.DevfileHeader - dw.DevWorkspaceTemplateSpec -} - -func ConvertDevfileToDevWorkspaceTemplate(devfile *Devfile) (*dw.DevWorkspaceTemplate, error) { +func ConvertDevfileToDevWorkspaceTemplate(devfile *dw.Devfile) (*dw.DevWorkspaceTemplate, error) { if !SupportedSchemaVersionRegexp.MatchString(devfile.SchemaVersion) { return nil, fmt.Errorf("could not process devfile: unsupported schemaVersion '%s'", devfile.SchemaVersion) } diff --git a/pkg/flatten/network/fetch.go b/pkg/flatten/network/fetch.go index 4131e21b..1551254e 100644 --- a/pkg/flatten/network/fetch.go +++ b/pkg/flatten/network/fetch.go @@ -41,7 +41,7 @@ func FetchDevWorkspaceTemplate(location string, httpClient HTTPGetter) (*dw.DevW } // Assume we're getting a devfile, not a DevWorkspaceTemplate (TODO: Detect type and handle both?) - devfile := &Devfile{} + devfile := &dw.Devfile{} err = yaml.Unmarshal(bytes, devfile) if err != nil { return nil, fmt.Errorf("could not unmarshal devfile from response: %w", err) From 0275f6cbeabc9f1047443ccd363e5674d960ea4e Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Fri, 19 Mar 2021 16:45:34 -0400 Subject: [PATCH 07/13] Add support for retreiving network resources as DevWorkspaces If attempting to unmarshal a network response to a devfile results in a struct without a schemaVersion, attempt to unmarshal to a DevWorkspace or DevWorkspaceTemplate instead. Signed-off-by: Angel Misevski --- pkg/flatten/network/fetch.go | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/pkg/flatten/network/fetch.go b/pkg/flatten/network/fetch.go index 1551254e..c0735879 100644 --- a/pkg/flatten/network/fetch.go +++ b/pkg/flatten/network/fetch.go @@ -40,17 +40,35 @@ func FetchDevWorkspaceTemplate(location string, httpClient HTTPGetter) (*dw.DevW return nil, fmt.Errorf("could not read data from %s: %w", location, err) } - // Assume we're getting a devfile, not a DevWorkspaceTemplate (TODO: Detect type and handle both?) devfile := &dw.Devfile{} - err = yaml.Unmarshal(bytes, devfile) - if err != nil { + if err := yaml.Unmarshal(bytes, devfile); err != nil { return nil, fmt.Errorf("could not unmarshal devfile from response: %w", err) } + if devfile.SchemaVersion != "" { + dwt, err := ConvertDevfileToDevWorkspaceTemplate(devfile) + if err != nil { + return nil, fmt.Errorf("failed to convert devfile to DevWorkspaceTemplate: %s", err) + } + return &dwt.Spec, nil + } - dwt, err := ConvertDevfileToDevWorkspaceTemplate(devfile) - if err != nil { - return nil, fmt.Errorf("failed to convert devfile to DevWorkspaceTemplate: %s", err) + // Assume we didn't get a devfile, check if content is DevWorkspace + devworkspace := &dw.DevWorkspace{} + if err := yaml.Unmarshal(bytes, devworkspace); err != nil { + return nil, fmt.Errorf("could not unmarshal devworkspace from response: %w", err) + } + if devworkspace.Kind == "DevWorkspace" { + return &devworkspace.Spec.Template, nil + } + + // Check if content is DevWorkspaceTemplate + dwt := &dw.DevWorkspaceTemplate{} + if err := yaml.Unmarshal(bytes, dwt); err != nil { + return nil, fmt.Errorf("could not unmarshal devworkspacetemplate from response: %w", err) + } + if dwt.Kind == "DevWorkspaceTemplate" { + return &dwt.Spec, nil } - return &dwt.Spec, nil + return nil, fmt.Errorf("could not find devfile or devworkspace object at '%s'", location) } From 8d4303695bda3bd12caf75daf71b9a180c25e6b3 Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Fri, 19 Mar 2021 17:41:17 -0400 Subject: [PATCH 08/13] Add test cases to cover devworkspace(templates) on URIs Signed-off-by: Angel Misevski --- pkg/flatten/flatten_test.go | 10 +++--- pkg/flatten/internal/testutil/http.go | 18 +++++++++-- .../error_fetch-unparseable-file.yaml | 23 +++++++++++++ ...solve-devworkspace-instead-of-devfile.yaml | 32 +++++++++++++++++++ .../error_fetch-unparseable-file.yaml | 22 +++++++++++++ ...solve-devworkspace-instead-of-devfile.yaml | 31 ++++++++++++++++++ 6 files changed, 129 insertions(+), 7 deletions(-) create mode 100644 pkg/flatten/testdata/plugin-id/error_fetch-unparseable-file.yaml create mode 100644 pkg/flatten/testdata/plugin-id/resolve-devworkspace-instead-of-devfile.yaml create mode 100644 pkg/flatten/testdata/plugin-uri/error_fetch-unparseable-file.yaml create mode 100644 pkg/flatten/testdata/plugin-uri/resolve-devworkspace-instead-of-devfile.yaml diff --git a/pkg/flatten/flatten_test.go b/pkg/flatten/flatten_test.go index cd70d241..85862d7f 100644 --- a/pkg/flatten/flatten_test.go +++ b/pkg/flatten/flatten_test.go @@ -58,8 +58,9 @@ func TestResolveDevWorkspacePluginRegistry(t *testing.T) { // sanity check: input defines components assert.True(t, len(tt.Input.Workspace.Components) > 0, "Test case defines workspace with no components") testHttpGetter := &testutil.FakeHTTPGetter{ - Plugins: tt.Input.DevfilePlugins, - Errors: tt.Input.Errors, + DevfilePlugins: tt.Input.DevfilePlugins, + DevWorkspacePlugins: tt.Input.Plugins, + Errors: tt.Input.Errors, } testResolverTools := ResolverTools{ Context: context.Background(), @@ -87,8 +88,9 @@ func TestResolveDevWorkspacePluginURI(t *testing.T) { // sanity check: input defines components assert.True(t, len(tt.Input.Workspace.Components) > 0, "Test case defines workspace with no components") testHttpGetter := &testutil.FakeHTTPGetter{ - Plugins: tt.Input.DevfilePlugins, - Errors: tt.Input.Errors, + DevfilePlugins: tt.Input.DevfilePlugins, + DevWorkspacePlugins: tt.Input.Plugins, + Errors: tt.Input.Errors, } testResolverTools := ResolverTools{ Context: context.Background(), diff --git a/pkg/flatten/internal/testutil/http.go b/pkg/flatten/internal/testutil/http.go index 05276953..c73caf4a 100644 --- a/pkg/flatten/internal/testutil/http.go +++ b/pkg/flatten/internal/testutil/http.go @@ -26,8 +26,9 @@ import ( ) type FakeHTTPGetter struct { - Plugins map[string]dw.Devfile - Errors map[string]TestPluginError + DevfilePlugins map[string]dw.Devfile + DevWorkspacePlugins map[string]dw.DevWorkspaceTemplate + Errors map[string]TestPluginError } var _ network.HTTPGetter = (*FakeHTTPGetter)(nil) @@ -39,7 +40,18 @@ type fakeRespBody struct { func (_ *fakeRespBody) Close() error { return nil } func (reg *FakeHTTPGetter) Get(location string) (*http.Response, error) { - if plugin, ok := reg.Plugins[location]; ok { + if plugin, ok := reg.DevfilePlugins[location]; ok { + yamlBytes, err := yaml.Marshal(plugin) + if err != nil { + return nil, fmt.Errorf("error marshalling plugin in test: %w", err) + } + resp := &http.Response{ + StatusCode: http.StatusOK, + Body: &fakeRespBody{bytes.NewBuffer(yamlBytes)}, + } + return resp, nil + } + if plugin, ok := reg.DevWorkspacePlugins[location]; ok { yamlBytes, err := yaml.Marshal(plugin) if err != nil { return nil, fmt.Errorf("error marshalling plugin in test: %w", err) diff --git a/pkg/flatten/testdata/plugin-id/error_fetch-unparseable-file.yaml b/pkg/flatten/testdata/plugin-id/error_fetch-unparseable-file.yaml new file mode 100644 index 00000000..570f2d1f --- /dev/null +++ b/pkg/flatten/testdata/plugin-id/error_fetch-unparseable-file.yaml @@ -0,0 +1,23 @@ +name: "DevWorkspace registry contains non-devfile type content" + +input: + workspace: + components: + - name: test-plugin + plugin: + id: my/test/plugin + registryUrl: "https://test-registry.io/subpath" + plugins: + "https://test-registry.io/subpath/my/test/plugin": + metadata: + name: test-plugin + spec: + components: + - name: plugin-a + container: + name: test-container + image: test-image + + +output: + errRegexp: "could not find devfile or devworkspace object at 'https://test-registry.io/subpath/my/test/plugin'" diff --git a/pkg/flatten/testdata/plugin-id/resolve-devworkspace-instead-of-devfile.yaml b/pkg/flatten/testdata/plugin-id/resolve-devworkspace-instead-of-devfile.yaml new file mode 100644 index 00000000..b4dbf17f --- /dev/null +++ b/pkg/flatten/testdata/plugin-id/resolve-devworkspace-instead-of-devfile.yaml @@ -0,0 +1,32 @@ +name: "DevWorkspace references DevWorkspaceTemplate plugin from registry" + +input: + workspace: + components: + - name: test-plugin + plugin: + id: my/test/plugin + registryUrl: "https://test-registry.io/subpath" + plugins: + "https://test-registry.io/subpath/my/test/plugin": + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: test-plugin + spec: + components: + - name: plugin-a + container: + name: test-container + image: test-image + + +output: + workspace: + components: + - name: plugin-a + attributes: + library.devfile.io/imported-by: "test-plugin" + container: + name: test-container + image: test-image diff --git a/pkg/flatten/testdata/plugin-uri/error_fetch-unparseable-file.yaml b/pkg/flatten/testdata/plugin-uri/error_fetch-unparseable-file.yaml new file mode 100644 index 00000000..2fc44054 --- /dev/null +++ b/pkg/flatten/testdata/plugin-uri/error_fetch-unparseable-file.yaml @@ -0,0 +1,22 @@ +name: "DevWorkspace reference URI containing non-devfile type content" + +input: + workspace: + components: + - name: test-plugin + plugin: + uri: "https://my-plugin.io/test" + plugins: + "https://my-plugin.io/test": + metadata: + name: test-plugin + spec: + components: + - name: plugin-a + container: + name: test-container + image: test-image + + +output: + errRegexp: "could not find devfile or devworkspace object at 'https://my-plugin.io/test'" diff --git a/pkg/flatten/testdata/plugin-uri/resolve-devworkspace-instead-of-devfile.yaml b/pkg/flatten/testdata/plugin-uri/resolve-devworkspace-instead-of-devfile.yaml new file mode 100644 index 00000000..7176e95d --- /dev/null +++ b/pkg/flatten/testdata/plugin-uri/resolve-devworkspace-instead-of-devfile.yaml @@ -0,0 +1,31 @@ +name: "DevWorkspace references DevWorkspaceTemplate plugin from registry" + +input: + workspace: + components: + - name: test-plugin + plugin: + uri: "https://my-plugin.io/test" + plugins: + "https://my-plugin.io/test": + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: test-plugin + spec: + components: + - name: plugin-a + container: + name: test-container + image: test-image + + +output: + workspace: + components: + - name: plugin-a + attributes: + library.devfile.io/imported-by: "test-plugin" + container: + name: test-container + image: test-image From ac61ee03e829bab526ec7540a656edb13caa4b3a Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Fri, 19 Mar 2021 17:51:23 -0400 Subject: [PATCH 09/13] Modify test struct names to accomodate parent tests Rename plugin-related struct fields in tests to be generic, to allow them to hold any resources referenced. Required for reusing these structs cleanly in parent tests. Signed-off-by: Angel Misevski --- pkg/flatten/flatten_test.go | 51 +++++++++++--- pkg/flatten/internal/testutil/common.go | 8 +-- pkg/flatten/internal/testutil/http.go | 10 +-- pkg/flatten/internal/testutil/k8sClient.go | 8 +-- .../k8s-ref/error_bad-plugin-merge.yaml | 2 +- .../k8s-ref/error_conflicting-merge.yaml | 2 +- .../testdata/k8s-ref/error_has-parent.yaml | 15 ---- .../k8s-ref/error_plugin-references-self.yml | 2 +- .../k8s-ref/error_plugins-have-cycle.yml | 2 +- .../k8s-ref/nested-plugins-annotation.yaml | 2 +- .../testdata/k8s-ref/nodejs-workspace.yaml | 2 +- .../k8s-ref/web-terminal-with-plugin.yaml | 2 +- .../parent/error_parent-has-parent.yaml | 31 +++++++++ .../parent/error_parent-has-plugins.yaml | 26 +++++++ .../parent/resolve-parent-and-plugins.yaml | 68 +++++++++++++++++++ .../testdata/parent/resolve-parent-by-id.yaml | 44 ++++++++++++ .../resolve-parent-by-k8s-reference.yaml | 46 +++++++++++++ .../parent/resolve-parent-by-uri.yaml | 43 ++++++++++++ .../error_fetch-unparseable-file.yaml | 2 +- .../error_invalid-schema-version.yaml | 2 +- ...solve-devworkspace-instead-of-devfile.yaml | 2 +- .../plugin-id/resolve-plugin-by-id.yaml | 2 +- .../resolve-plugin-multiple-registries.yaml | 2 +- .../error_fetch-unparseable-file.yaml | 2 +- .../error_invalid-schema-version.yaml | 2 +- ...solve-devworkspace-instead-of-devfile.yaml | 2 +- .../resolve-multiple-plugins-by-uri.yaml | 2 +- .../plugin-uri/resolve-plugin-by-uri.yaml | 2 +- 28 files changed, 331 insertions(+), 53 deletions(-) delete mode 100644 pkg/flatten/testdata/k8s-ref/error_has-parent.yaml create mode 100644 pkg/flatten/testdata/parent/error_parent-has-parent.yaml create mode 100644 pkg/flatten/testdata/parent/error_parent-has-plugins.yaml create mode 100644 pkg/flatten/testdata/parent/resolve-parent-and-plugins.yaml create mode 100644 pkg/flatten/testdata/parent/resolve-parent-by-id.yaml create mode 100644 pkg/flatten/testdata/parent/resolve-parent-by-k8s-reference.yaml create mode 100644 pkg/flatten/testdata/parent/resolve-parent-by-uri.yaml diff --git a/pkg/flatten/flatten_test.go b/pkg/flatten/flatten_test.go index 85862d7f..7b1c1642 100644 --- a/pkg/flatten/flatten_test.go +++ b/pkg/flatten/flatten_test.go @@ -29,8 +29,8 @@ func TestResolveDevWorkspaceKubernetesReference(t *testing.T) { // sanity check: input defines components assert.True(t, len(tt.Input.Workspace.Components) > 0, "Test case defines workspace with no components") testClient := &testutil.FakeK8sClient{ - Plugins: tt.Input.Plugins, - Errors: tt.Input.Errors, + DevWorkspaceResources: tt.Input.DevWorkspaceResources, + Errors: tt.Input.Errors, } testResolverTools := ResolverTools{ Context: context.Background(), @@ -58,9 +58,9 @@ func TestResolveDevWorkspacePluginRegistry(t *testing.T) { // sanity check: input defines components assert.True(t, len(tt.Input.Workspace.Components) > 0, "Test case defines workspace with no components") testHttpGetter := &testutil.FakeHTTPGetter{ - DevfilePlugins: tt.Input.DevfilePlugins, - DevWorkspacePlugins: tt.Input.Plugins, - Errors: tt.Input.Errors, + DevfileResources: tt.Input.DevfileResources, + DevWorkspaceResources: tt.Input.DevWorkspaceResources, + Errors: tt.Input.Errors, } testResolverTools := ResolverTools{ Context: context.Background(), @@ -88,9 +88,9 @@ func TestResolveDevWorkspacePluginURI(t *testing.T) { // sanity check: input defines components assert.True(t, len(tt.Input.Workspace.Components) > 0, "Test case defines workspace with no components") testHttpGetter := &testutil.FakeHTTPGetter{ - DevfilePlugins: tt.Input.DevfilePlugins, - DevWorkspacePlugins: tt.Input.Plugins, - Errors: tt.Input.Errors, + DevfileResources: tt.Input.DevfileResources, + DevWorkspaceResources: tt.Input.DevWorkspaceResources, + Errors: tt.Input.Errors, } testResolverTools := ResolverTools{ Context: context.Background(), @@ -110,3 +110,38 @@ func TestResolveDevWorkspacePluginURI(t *testing.T) { }) } } + +func TestResolveDevWorkspaceParents(t *testing.T) { + tests := testutil.LoadAllTestsOrPanic(t, "testdata/parent") + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + // sanity check: input defines components + assert.True(t, len(tt.Input.Workspace.Components) > 0, "Test case defines workspace with no components") + testHttpGetter := &testutil.FakeHTTPGetter{ + DevfileResources: tt.Input.DevfileResources, + DevWorkspaceResources: tt.Input.DevWorkspaceResources, + Errors: tt.Input.Errors, + } + testK8sClient := &testutil.FakeK8sClient{ + DevWorkspaceResources: tt.Input.DevWorkspaceResources, + Errors: tt.Input.Errors, + } + testResolverTools := ResolverTools{ + Context: context.Background(), + K8sClient: testK8sClient, + HttpClient: testHttpGetter, + } + outputWorkspace, err := ResolveDevWorkspace(&tt.Input.Workspace, testResolverTools) + if tt.Output.ErrRegexp != nil && assert.Error(t, err) { + assert.Regexp(t, *tt.Output.ErrRegexp, err.Error(), "Error message should match") + } else { + if !assert.NoError(t, err, "Should not return error") { + return + } + assert.Truef(t, cmp.Equal(tt.Output.Workspace, outputWorkspace, testutil.WorkspaceTemplateDiffOpts), + "Workspace should match expected output:\n%s", + cmp.Diff(tt.Output.Workspace, outputWorkspace, testutil.WorkspaceTemplateDiffOpts)) + } + }) + } +} diff --git a/pkg/flatten/internal/testutil/common.go b/pkg/flatten/internal/testutil/common.go index 289ca5d6..fe0007a5 100644 --- a/pkg/flatten/internal/testutil/common.go +++ b/pkg/flatten/internal/testutil/common.go @@ -48,10 +48,10 @@ type TestCase struct { // TestInput defines the inputs required for a test case. type TestInput struct { Workspace dw.DevWorkspaceTemplateSpec `json:"workspace,omitempty"` - // Plugins is a map of plugin "name" to devworkspace template; namespace is ignored. - Plugins map[string]dw.DevWorkspaceTemplate `json:"plugins,omitempty"` - // DevfilePlugins is a map of plugin "name" to devfile - DevfilePlugins map[string]dw.Devfile `json:"devfilePlugins,omitempty"` + // DevWorkspaceResources is a map of string keys to devworkspace templates + DevWorkspaceResources map[string]dw.DevWorkspaceTemplate `json:"devworkspaceResources,omitempty"` + // DevfileResources is a map of string keys to devfile resources + DevfileResources map[string]dw.Devfile `json:"devfileResources,omitempty"` // Errors is a map of plugin name to the error that should be returned when attempting to retrieve it. Errors map[string]TestPluginError `json:"errors,omitempty"` } diff --git a/pkg/flatten/internal/testutil/http.go b/pkg/flatten/internal/testutil/http.go index c73caf4a..8544134d 100644 --- a/pkg/flatten/internal/testutil/http.go +++ b/pkg/flatten/internal/testutil/http.go @@ -26,9 +26,9 @@ import ( ) type FakeHTTPGetter struct { - DevfilePlugins map[string]dw.Devfile - DevWorkspacePlugins map[string]dw.DevWorkspaceTemplate - Errors map[string]TestPluginError + DevfileResources map[string]dw.Devfile + DevWorkspaceResources map[string]dw.DevWorkspaceTemplate + Errors map[string]TestPluginError } var _ network.HTTPGetter = (*FakeHTTPGetter)(nil) @@ -40,7 +40,7 @@ type fakeRespBody struct { func (_ *fakeRespBody) Close() error { return nil } func (reg *FakeHTTPGetter) Get(location string) (*http.Response, error) { - if plugin, ok := reg.DevfilePlugins[location]; ok { + if plugin, ok := reg.DevfileResources[location]; ok { yamlBytes, err := yaml.Marshal(plugin) if err != nil { return nil, fmt.Errorf("error marshalling plugin in test: %w", err) @@ -51,7 +51,7 @@ func (reg *FakeHTTPGetter) Get(location string) (*http.Response, error) { } return resp, nil } - if plugin, ok := reg.DevWorkspacePlugins[location]; ok { + if plugin, ok := reg.DevWorkspaceResources[location]; ok { yamlBytes, err := yaml.Marshal(plugin) if err != nil { return nil, fmt.Errorf("error marshalling plugin in test: %w", err) diff --git a/pkg/flatten/internal/testutil/k8sClient.go b/pkg/flatten/internal/testutil/k8sClient.go index 1694c755..1acab810 100644 --- a/pkg/flatten/internal/testutil/k8sClient.go +++ b/pkg/flatten/internal/testutil/k8sClient.go @@ -25,9 +25,9 @@ import ( ) type FakeK8sClient struct { - client.Client // To satisfy interface; override all used methods - Plugins map[string]v1alpha2.DevWorkspaceTemplate - Errors map[string]TestPluginError + client.Client // To satisfy interface; override all used methods + DevWorkspaceResources map[string]v1alpha2.DevWorkspaceTemplate + Errors map[string]TestPluginError } func (client *FakeK8sClient) Get(_ context.Context, namespacedName client.ObjectKey, obj runtime.Object) error { @@ -35,7 +35,7 @@ func (client *FakeK8sClient) Get(_ context.Context, namespacedName client.Object if !ok { return fmt.Errorf("called Get() in fake client with non-DevWorkspaceTemplate") } - if plugin, ok := client.Plugins[namespacedName.Name]; ok { + if plugin, ok := client.DevWorkspaceResources[namespacedName.Name]; ok { *template = plugin return nil } diff --git a/pkg/flatten/testdata/k8s-ref/error_bad-plugin-merge.yaml b/pkg/flatten/testdata/k8s-ref/error_bad-plugin-merge.yaml index 97df12ef..f5fdb20c 100644 --- a/pkg/flatten/testdata/k8s-ref/error_bad-plugin-merge.yaml +++ b/pkg/flatten/testdata/k8s-ref/error_bad-plugin-merge.yaml @@ -11,7 +11,7 @@ input: - name: non-existent container: memoryLimit: 512Mi - plugins: + devworkspaceResources: override: spec: components: diff --git a/pkg/flatten/testdata/k8s-ref/error_conflicting-merge.yaml b/pkg/flatten/testdata/k8s-ref/error_conflicting-merge.yaml index a41d0ea0..0c096bca 100644 --- a/pkg/flatten/testdata/k8s-ref/error_conflicting-merge.yaml +++ b/pkg/flatten/testdata/k8s-ref/error_conflicting-merge.yaml @@ -10,7 +10,7 @@ input: - name: my-component container: image: test-image - plugins: + devworkspaceResources: test-plugin: spec: components: diff --git a/pkg/flatten/testdata/k8s-ref/error_has-parent.yaml b/pkg/flatten/testdata/k8s-ref/error_has-parent.yaml deleted file mode 100644 index 36031cee..00000000 --- a/pkg/flatten/testdata/k8s-ref/error_has-parent.yaml +++ /dev/null @@ -1,15 +0,0 @@ -name: "Workspace has parent" - -input: - workspace: - parent: - kubernetes: - name: my-parent - components: - - name: my-component - container: - image: test-image - - -output: - errRegexp: "DevWorkspace parent is unsupported" diff --git a/pkg/flatten/testdata/k8s-ref/error_plugin-references-self.yml b/pkg/flatten/testdata/k8s-ref/error_plugin-references-self.yml index c551e576..61084f17 100644 --- a/pkg/flatten/testdata/k8s-ref/error_plugin-references-self.yml +++ b/pkg/flatten/testdata/k8s-ref/error_plugin-references-self.yml @@ -7,7 +7,7 @@ input: plugin: kubernetes: name: plugin-a - plugins: + devworkspaceResources: plugin-a: kind: DevWorkspaceTemplate apiVersion: workspace.devfile.io/v1alpha2 diff --git a/pkg/flatten/testdata/k8s-ref/error_plugins-have-cycle.yml b/pkg/flatten/testdata/k8s-ref/error_plugins-have-cycle.yml index 9a8f8ee0..f6fcfff1 100644 --- a/pkg/flatten/testdata/k8s-ref/error_plugins-have-cycle.yml +++ b/pkg/flatten/testdata/k8s-ref/error_plugins-have-cycle.yml @@ -7,7 +7,7 @@ input: plugin: kubernetes: name: plugin-a - plugins: + devworkspaceResources: plugin-a: kind: DevWorkspaceTemplate apiVersion: workspace.devfile.io/v1alpha2 diff --git a/pkg/flatten/testdata/k8s-ref/nested-plugins-annotation.yaml b/pkg/flatten/testdata/k8s-ref/nested-plugins-annotation.yaml index 37d4964c..e058343c 100644 --- a/pkg/flatten/testdata/k8s-ref/nested-plugins-annotation.yaml +++ b/pkg/flatten/testdata/k8s-ref/nested-plugins-annotation.yaml @@ -8,7 +8,7 @@ input: kubernetes: name: test-plugin-a namespace: test-ns - plugins: + devworkspaceResources: test-plugin-a: kind: DevWorkspaceTemplate apiVersion: workspace.devfile.io/v1alpha2 diff --git a/pkg/flatten/testdata/k8s-ref/nodejs-workspace.yaml b/pkg/flatten/testdata/k8s-ref/nodejs-workspace.yaml index 5fa2e833..18cd0740 100644 --- a/pkg/flatten/testdata/k8s-ref/nodejs-workspace.yaml +++ b/pkg/flatten/testdata/k8s-ref/nodejs-workspace.yaml @@ -76,7 +76,7 @@ input: } ] } - plugins: + devworkspaceResources: theia-next: kind: DevWorkspaceTemplate apiVersion: workspace.devfile.io/v1alpha2 diff --git a/pkg/flatten/testdata/k8s-ref/web-terminal-with-plugin.yaml b/pkg/flatten/testdata/k8s-ref/web-terminal-with-plugin.yaml index e30e74de..4298264d 100644 --- a/pkg/flatten/testdata/k8s-ref/web-terminal-with-plugin.yaml +++ b/pkg/flatten/testdata/k8s-ref/web-terminal-with-plugin.yaml @@ -16,7 +16,7 @@ input: kubernetes: name: web-terminal namespace: devworkspace-plugins - plugins: + devworkspaceResources: web-terminal: kind: DevWorkspaceTemplate apiVersion: workspace.devfile.io/v1alpha2 diff --git a/pkg/flatten/testdata/parent/error_parent-has-parent.yaml b/pkg/flatten/testdata/parent/error_parent-has-parent.yaml new file mode 100644 index 00000000..4de009da --- /dev/null +++ b/pkg/flatten/testdata/parent/error_parent-has-parent.yaml @@ -0,0 +1,31 @@ +name: "Fails when parent is not flattened (has parent)" + +input: + workspace: + parent: + kubernetes: + name: test-parent-k8s + components: + - name: regular-component + container: + image: regular-test-image + name: regular-container + devworkspaceResources: + test-parent-k8s: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: parent-devworkspacetemplate + spec: + parent: + id: another-parent + components: + - name: parent-component + container: + image: test-img + env: + - name: test-env + value: original-value + +output: + errRegexp: "parents containing plugins or parents are not supported" diff --git a/pkg/flatten/testdata/parent/error_parent-has-plugins.yaml b/pkg/flatten/testdata/parent/error_parent-has-plugins.yaml new file mode 100644 index 00000000..b8805106 --- /dev/null +++ b/pkg/flatten/testdata/parent/error_parent-has-plugins.yaml @@ -0,0 +1,26 @@ +name: "Fails when parent is not flattened (has plugin)" + +input: + workspace: + parent: + kubernetes: + name: test-parent-k8s + components: + - name: regular-component + container: + image: regular-test-image + name: regular-container + devworkspaceResources: + test-parent-k8s: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: parent-devworkspacetemplate + spec: + components: + - name: parent-component + plugin: + id: parent-plugin + +output: + errRegexp: "parents containing plugins or parents are not supported" diff --git a/pkg/flatten/testdata/parent/resolve-parent-and-plugins.yaml b/pkg/flatten/testdata/parent/resolve-parent-and-plugins.yaml new file mode 100644 index 00000000..107da0e6 --- /dev/null +++ b/pkg/flatten/testdata/parent/resolve-parent-and-plugins.yaml @@ -0,0 +1,68 @@ +name: "Resolve parent and plugins" + +input: + workspace: + parent: + kubernetes: + name: test-parent-k8s + components: + - name: parent-component + container: + env: + - name: test-env + value: test-value + components: + - name: regular-component + container: + image: regular-test-image + name: regular-container + - name: test-plugin + plugin: + uri: https://test-plugin.io/test-plugin + devworkspaceResources: + test-parent-k8s: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: parent-devworkspacetemplate + spec: + components: + - name: parent-component + container: + image: test-img + env: + - name: test-env + value: original-value + devfileResources: + "https://test-plugin.io/test-plugin": + schemaVersion: 2.1.0 + metadata: + name: test-plugin + components: + - name: plugin-component + container: + image: plugin-img + env: + - name: plugin-env + value: original-value +output: + workspace: + components: + - name: parent-component + container: + image: test-img + env: + - name: test-env + value: test-value + - name: regular-component + container: + image: regular-test-image + name: regular-container + - name: plugin-component + attributes: + library.devfile.io/imported-by: test-plugin + container: + image: plugin-img + env: + - name: plugin-env + value: original-value diff --git a/pkg/flatten/testdata/parent/resolve-parent-by-id.yaml b/pkg/flatten/testdata/parent/resolve-parent-by-id.yaml new file mode 100644 index 00000000..2ddfbf35 --- /dev/null +++ b/pkg/flatten/testdata/parent/resolve-parent-by-id.yaml @@ -0,0 +1,44 @@ +name: "Resolve parent by ID" + +input: + workspace: + parent: + id: test/parent/id + registryUrl: https://test-registry.io/ + components: + - name: parent-component + container: + env: + - name: test-env + value: test-value + components: + - name: regular-component + container: + image: regular-test-image + name: regular-container + devfileResources: + "https://test-registry.io/test/parent/id": + schemaVersion: 2.1.0 + metadata: + name: parent-devfile + components: + - name: parent-component + container: + image: test-img + env: + - name: test-env + value: original-value + +output: + workspace: + components: + - name: parent-component + container: + image: test-img + env: + - name: test-env + value: test-value + - name: regular-component + container: + image: regular-test-image + name: regular-container diff --git a/pkg/flatten/testdata/parent/resolve-parent-by-k8s-reference.yaml b/pkg/flatten/testdata/parent/resolve-parent-by-k8s-reference.yaml new file mode 100644 index 00000000..2b7dea41 --- /dev/null +++ b/pkg/flatten/testdata/parent/resolve-parent-by-k8s-reference.yaml @@ -0,0 +1,46 @@ +name: "Resolve parent by Kubernetes reference" + +input: + workspace: + parent: + kubernetes: + name: test-parent-k8s + components: + - name: parent-component + container: + env: + - name: test-env + value: test-value + components: + - name: regular-component + container: + image: regular-test-image + name: regular-container + devworkspaceResources: + test-parent-k8s: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: parent-devworkspacetemplate + spec: + components: + - name: parent-component + container: + image: test-img + env: + - name: test-env + value: original-value + +output: + workspace: + components: + - name: parent-component + container: + image: test-img + env: + - name: test-env + value: test-value + - name: regular-component + container: + image: regular-test-image + name: regular-container diff --git a/pkg/flatten/testdata/parent/resolve-parent-by-uri.yaml b/pkg/flatten/testdata/parent/resolve-parent-by-uri.yaml new file mode 100644 index 00000000..e10bc48e --- /dev/null +++ b/pkg/flatten/testdata/parent/resolve-parent-by-uri.yaml @@ -0,0 +1,43 @@ +name: "Resolve parent by URI" + +input: + workspace: + parent: + uri: https://test.io/path/to/parent + components: + - name: parent-component + container: + env: + - name: test-env + value: test-value + components: + - name: regular-component + container: + image: regular-test-image + name: regular-container + devfileResources: + "https://test.io/path/to/parent": + schemaVersion: 2.1.0 + metadata: + name: parent-devfile + components: + - name: parent-component + container: + image: test-img + env: + - name: test-env + value: original-value + +output: + workspace: + components: + - name: parent-component + container: + image: test-img + env: + - name: test-env + value: test-value + - name: regular-component + container: + image: regular-test-image + name: regular-container diff --git a/pkg/flatten/testdata/plugin-id/error_fetch-unparseable-file.yaml b/pkg/flatten/testdata/plugin-id/error_fetch-unparseable-file.yaml index 570f2d1f..c2b0ed51 100644 --- a/pkg/flatten/testdata/plugin-id/error_fetch-unparseable-file.yaml +++ b/pkg/flatten/testdata/plugin-id/error_fetch-unparseable-file.yaml @@ -7,7 +7,7 @@ input: plugin: id: my/test/plugin registryUrl: "https://test-registry.io/subpath" - plugins: + devworkspaceResources: "https://test-registry.io/subpath/my/test/plugin": metadata: name: test-plugin diff --git a/pkg/flatten/testdata/plugin-id/error_invalid-schema-version.yaml b/pkg/flatten/testdata/plugin-id/error_invalid-schema-version.yaml index 4eaff077..ceb400f1 100644 --- a/pkg/flatten/testdata/plugin-id/error_invalid-schema-version.yaml +++ b/pkg/flatten/testdata/plugin-id/error_invalid-schema-version.yaml @@ -7,7 +7,7 @@ input: plugin: id: my/test/plugin registryUrl: "https://test-registry.io/subpath" - devfilePlugins: + devfileResources: "https://test-registry.io/subpath/my/test/plugin": schemaVersion: 1.0.0 metadata: diff --git a/pkg/flatten/testdata/plugin-id/resolve-devworkspace-instead-of-devfile.yaml b/pkg/flatten/testdata/plugin-id/resolve-devworkspace-instead-of-devfile.yaml index b4dbf17f..669964d6 100644 --- a/pkg/flatten/testdata/plugin-id/resolve-devworkspace-instead-of-devfile.yaml +++ b/pkg/flatten/testdata/plugin-id/resolve-devworkspace-instead-of-devfile.yaml @@ -7,7 +7,7 @@ input: plugin: id: my/test/plugin registryUrl: "https://test-registry.io/subpath" - plugins: + devworkspaceResources: "https://test-registry.io/subpath/my/test/plugin": kind: DevWorkspaceTemplate apiVersion: workspace.devfile.io/v1alpha2 diff --git a/pkg/flatten/testdata/plugin-id/resolve-plugin-by-id.yaml b/pkg/flatten/testdata/plugin-id/resolve-plugin-by-id.yaml index 82135905..6cb7a9fb 100644 --- a/pkg/flatten/testdata/plugin-id/resolve-plugin-by-id.yaml +++ b/pkg/flatten/testdata/plugin-id/resolve-plugin-by-id.yaml @@ -7,7 +7,7 @@ input: plugin: id: my/test/plugin registryUrl: "https://test-registry.io/subpath" - devfilePlugins: + devfileResources: "https://test-registry.io/subpath/my/test/plugin": schemaVersion: 2.0.0 metadata: diff --git a/pkg/flatten/testdata/plugin-id/resolve-plugin-multiple-registries.yaml b/pkg/flatten/testdata/plugin-id/resolve-plugin-multiple-registries.yaml index ec7f4e0c..0503c009 100644 --- a/pkg/flatten/testdata/plugin-id/resolve-plugin-multiple-registries.yaml +++ b/pkg/flatten/testdata/plugin-id/resolve-plugin-multiple-registries.yaml @@ -11,7 +11,7 @@ input: plugin: id: my/test/plugin-2 registryUrl: "https://test-registry-2.io/subpath" - devfilePlugins: + devfileResources: "https://test-registry.io/subpath/my/test/plugin": schemaVersion: 2.0.0 metadata: diff --git a/pkg/flatten/testdata/plugin-uri/error_fetch-unparseable-file.yaml b/pkg/flatten/testdata/plugin-uri/error_fetch-unparseable-file.yaml index 2fc44054..c4aa2e2e 100644 --- a/pkg/flatten/testdata/plugin-uri/error_fetch-unparseable-file.yaml +++ b/pkg/flatten/testdata/plugin-uri/error_fetch-unparseable-file.yaml @@ -6,7 +6,7 @@ input: - name: test-plugin plugin: uri: "https://my-plugin.io/test" - plugins: + devworkspaceResources: "https://my-plugin.io/test": metadata: name: test-plugin diff --git a/pkg/flatten/testdata/plugin-uri/error_invalid-schema-version.yaml b/pkg/flatten/testdata/plugin-uri/error_invalid-schema-version.yaml index ea5f777a..9a92e17d 100644 --- a/pkg/flatten/testdata/plugin-uri/error_invalid-schema-version.yaml +++ b/pkg/flatten/testdata/plugin-uri/error_invalid-schema-version.yaml @@ -6,7 +6,7 @@ input: - name: test-plugin plugin: uri: https://test-registry.io/old-devfiles - devfilePlugins: + devfileResources: "https://test-registry.io/old-devfiles": schemaVersion: 1.0.0 metadata: diff --git a/pkg/flatten/testdata/plugin-uri/resolve-devworkspace-instead-of-devfile.yaml b/pkg/flatten/testdata/plugin-uri/resolve-devworkspace-instead-of-devfile.yaml index 7176e95d..025d1753 100644 --- a/pkg/flatten/testdata/plugin-uri/resolve-devworkspace-instead-of-devfile.yaml +++ b/pkg/flatten/testdata/plugin-uri/resolve-devworkspace-instead-of-devfile.yaml @@ -6,7 +6,7 @@ input: - name: test-plugin plugin: uri: "https://my-plugin.io/test" - plugins: + devworkspaceResources: "https://my-plugin.io/test": kind: DevWorkspaceTemplate apiVersion: workspace.devfile.io/v1alpha2 diff --git a/pkg/flatten/testdata/plugin-uri/resolve-multiple-plugins-by-uri.yaml b/pkg/flatten/testdata/plugin-uri/resolve-multiple-plugins-by-uri.yaml index c137e363..741873fd 100644 --- a/pkg/flatten/testdata/plugin-uri/resolve-multiple-plugins-by-uri.yaml +++ b/pkg/flatten/testdata/plugin-uri/resolve-multiple-plugins-by-uri.yaml @@ -9,7 +9,7 @@ input: - name: test-plugin-2 plugin: uri: "https://my-plugin-alt.io/test" - devfilePlugins: + devfileResources: "https://my-plugin.io/test": schemaVersion: 2.0.0 metadata: diff --git a/pkg/flatten/testdata/plugin-uri/resolve-plugin-by-uri.yaml b/pkg/flatten/testdata/plugin-uri/resolve-plugin-by-uri.yaml index f20c525c..a1784cd4 100644 --- a/pkg/flatten/testdata/plugin-uri/resolve-plugin-by-uri.yaml +++ b/pkg/flatten/testdata/plugin-uri/resolve-plugin-by-uri.yaml @@ -6,7 +6,7 @@ input: - name: test-plugin plugin: uri: "https://my-plugin.io/test" - devfilePlugins: + devfileResources: "https://my-plugin.io/test": schemaVersion: 2.0.0 metadata: From 253a7280e1b2e3b8b88419c46eee9fb2217b9108 Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Fri, 19 Mar 2021 18:35:11 -0400 Subject: [PATCH 10/13] Make tooling fields optional and fail gracefully Make flatten library work without an HTTP or Kubernetes client to allow supporting only kubernetes or http references. If attempting to flatten a devfile that contains an import that doesn't have the required client, return a descriptive error. Signed-off-by: Angel Misevski --- pkg/flatten/flatten.go | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/pkg/flatten/flatten.go b/pkg/flatten/flatten.go index 45e37fd9..6bf8fb48 100644 --- a/pkg/flatten/flatten.go +++ b/pkg/flatten/flatten.go @@ -187,14 +187,26 @@ func resolvePluginComponent( func resolveElementByKubernetesImport( name string, kubeReference *devfile.KubernetesCustomResourceImportReference, - tooling ResolverTools) (resolvedPlugin *devfile.DevWorkspaceTemplateSpec, err error) { + tools ResolverTools) (resolvedPlugin *devfile.DevWorkspaceTemplateSpec, err error) { + + if tools.K8sClient == nil { + return nil, fmt.Errorf("cannot resolve resources by kubernetes reference: no kubernetes client provided") + } + + namespace := kubeReference.Namespace + if namespace == "" { + if tools.DefaultNamespace == "" { + return nil, fmt.Errorf("'%s' specifies a kubernetes reference without namespace and a default is not provided", name) + } + namespace = tools.DefaultNamespace + } var dwTemplate devfile.DevWorkspaceTemplate namespacedName := types.NamespacedName{ Name: kubeReference.Name, - Namespace: kubeReference.Namespace, + Namespace: namespace, } - err = tooling.K8sClient.Get(tooling.Context, namespacedName, &dwTemplate) + err = tools.K8sClient.Get(tools.Context, namespacedName, &dwTemplate) if err != nil { if errors.IsNotFound(err) { return nil, fmt.Errorf("plugin for component %s not found", name) @@ -213,8 +225,14 @@ func resolveElementById( registryUrl string, tools ResolverTools) (resolvedPlugin *devfile.DevWorkspaceTemplateSpec, err error) { - // TODO: Default registry when empty + if tools.HttpClient == nil { + return nil, fmt.Errorf("cannot resolve resources by id: no HTTP client provided") + } + if registryUrl == "" { + if tools.DefaultRegistryURL == "" { + return nil, fmt.Errorf("'%s' specifies id but has no registryUrl and a default is not provided", name) + } registryUrl = tools.DefaultRegistryURL } pluginURL, err := url.Parse(registryUrl) @@ -237,6 +255,10 @@ func resolveElementByURI( uri string, tools ResolverTools) (resolvedPlugin *devfile.DevWorkspaceTemplateSpec, err error) { + if tools.HttpClient == nil { + return nil, fmt.Errorf("cannot resolve resources by URI: no HTTP client provided") + } + dwt, err := network.FetchDevWorkspaceTemplate(uri, tools.HttpClient) if err != nil { return nil, fmt.Errorf("failed to resolve component %s by URI: %w", name, err) From d33c7285a6d589790df05d24abf7fa221be37d50 Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Fri, 19 Mar 2021 19:06:39 -0400 Subject: [PATCH 11/13] Add test cases to cover cases where incomplete tooling is provided Adds test cases to cover: - Using resolver when HttpClient or K8sClient are nil - Resolving Kubernetes resources when no namespace can be determined - Resolving resources by ID when no registryURL can be determined Signed-off-by: Angel Misevski --- pkg/flatten/flatten_test.go | 80 ++++++++++++++++++- ...icely-when-no-http-client-provided_id.yaml | 11 +++ ...cely-when-no-http-client-provided_uri.yaml | 11 +++ ...il-nicely-when-no-k8s-client-provided.yaml | 13 +++ .../fail-nicely-when-no-namespace.yaml | 12 +++ .../fail-nicely-when-no-registry-url.yaml | 11 +++ 6 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 pkg/flatten/testdata/general/fail-nicely-when-no-http-client-provided_id.yaml create mode 100644 pkg/flatten/testdata/general/fail-nicely-when-no-http-client-provided_uri.yaml create mode 100644 pkg/flatten/testdata/general/fail-nicely-when-no-k8s-client-provided.yaml create mode 100644 pkg/flatten/testdata/general/fail-nicely-when-no-namespace.yaml create mode 100644 pkg/flatten/testdata/general/fail-nicely-when-no-registry-url.yaml diff --git a/pkg/flatten/flatten_test.go b/pkg/flatten/flatten_test.go index 7b1c1642..e284f855 100644 --- a/pkg/flatten/flatten_test.go +++ b/pkg/flatten/flatten_test.go @@ -33,8 +33,9 @@ func TestResolveDevWorkspaceKubernetesReference(t *testing.T) { Errors: tt.Input.Errors, } testResolverTools := ResolverTools{ - Context: context.Background(), - K8sClient: testClient, + Context: context.Background(), + K8sClient: testClient, + DefaultNamespace: "default-namespace", } outputWorkspace, err := ResolveDevWorkspace(&tt.Input.Workspace, testResolverTools) if tt.Output.ErrRegexp != nil && assert.Error(t, err) { @@ -63,8 +64,9 @@ func TestResolveDevWorkspacePluginRegistry(t *testing.T) { Errors: tt.Input.Errors, } testResolverTools := ResolverTools{ - Context: context.Background(), - HttpClient: testHttpGetter, + Context: context.Background(), + HttpClient: testHttpGetter, + DefaultRegistryURL: "https://default-registry.com", } outputWorkspace, err := ResolveDevWorkspace(&tt.Input.Workspace, testResolverTools) if tt.Output.ErrRegexp != nil && assert.Error(t, err) { @@ -113,6 +115,76 @@ func TestResolveDevWorkspacePluginURI(t *testing.T) { func TestResolveDevWorkspaceParents(t *testing.T) { tests := testutil.LoadAllTestsOrPanic(t, "testdata/parent") + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + // sanity check: input defines components + assert.True(t, len(tt.Input.Workspace.Components) > 0, "Test case defines workspace with no components") + testHttpGetter := &testutil.FakeHTTPGetter{ + DevfileResources: tt.Input.DevfileResources, + DevWorkspaceResources: tt.Input.DevWorkspaceResources, + Errors: tt.Input.Errors, + } + testK8sClient := &testutil.FakeK8sClient{ + DevWorkspaceResources: tt.Input.DevWorkspaceResources, + Errors: tt.Input.Errors, + } + testResolverTools := ResolverTools{ + Context: context.Background(), + K8sClient: testK8sClient, + HttpClient: testHttpGetter, + DefaultNamespace: "default-namespace", + DefaultRegistryURL: "default-registry-url", + } + outputWorkspace, err := ResolveDevWorkspace(&tt.Input.Workspace, testResolverTools) + if tt.Output.ErrRegexp != nil && assert.Error(t, err) { + assert.Regexp(t, *tt.Output.ErrRegexp, err.Error(), "Error message should match") + } else { + if !assert.NoError(t, err, "Should not return error") { + return + } + assert.Truef(t, cmp.Equal(tt.Output.Workspace, outputWorkspace, testutil.WorkspaceTemplateDiffOpts), + "Workspace should match expected output:\n%s", + cmp.Diff(tt.Output.Workspace, outputWorkspace, testutil.WorkspaceTemplateDiffOpts)) + } + }) + } +} + +func TestResolveDevWorkspaceGeneralAPI(t *testing.T) { + tests := []testutil.TestCase{ + testutil.LoadTestCaseOrPanic(t, "testdata/general/fail-nicely-when-no-http-client-provided_id.yaml"), + testutil.LoadTestCaseOrPanic(t, "testdata/general/fail-nicely-when-no-http-client-provided_uri.yaml"), + testutil.LoadTestCaseOrPanic(t, "testdata/general/fail-nicely-when-no-k8s-client-provided.yaml"), + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + // sanity check: input defines components + assert.True(t, len(tt.Input.Workspace.Components) > 0, "Test case defines workspace with no components") + testResolverTools := ResolverTools{ + Context: context.Background(), + K8sClient: nil, + HttpClient: nil, + } + outputWorkspace, err := ResolveDevWorkspace(&tt.Input.Workspace, testResolverTools) + if tt.Output.ErrRegexp != nil && assert.Error(t, err) { + assert.Regexp(t, *tt.Output.ErrRegexp, err.Error(), "Error message should match") + } else { + if !assert.NoError(t, err, "Should not return error") { + return + } + assert.Truef(t, cmp.Equal(tt.Output.Workspace, outputWorkspace, testutil.WorkspaceTemplateDiffOpts), + "Workspace should match expected output:\n%s", + cmp.Diff(tt.Output.Workspace, outputWorkspace, testutil.WorkspaceTemplateDiffOpts)) + } + }) + } +} + +func TestResolveDevWorkspaceMissingDefaults(t *testing.T) { + tests := []testutil.TestCase{ + testutil.LoadTestCaseOrPanic(t, "testdata/general/fail-nicely-when-no-registry-url.yaml"), + testutil.LoadTestCaseOrPanic(t, "testdata/general/fail-nicely-when-no-namespace.yaml"), + } for _, tt := range tests { t.Run(tt.Name, func(t *testing.T) { // sanity check: input defines components diff --git a/pkg/flatten/testdata/general/fail-nicely-when-no-http-client-provided_id.yaml b/pkg/flatten/testdata/general/fail-nicely-when-no-http-client-provided_id.yaml new file mode 100644 index 00000000..f65b0428 --- /dev/null +++ b/pkg/flatten/testdata/general/fail-nicely-when-no-http-client-provided_id.yaml @@ -0,0 +1,11 @@ +name: "Fails nicely when no HTTP client provided and id is used" + +input: + workspace: + components: + - name: test-plugin + plugin: + id: test/plugin + +output: + errRegexp: "cannot resolve resources by id: no HTTP client provided" \ No newline at end of file diff --git a/pkg/flatten/testdata/general/fail-nicely-when-no-http-client-provided_uri.yaml b/pkg/flatten/testdata/general/fail-nicely-when-no-http-client-provided_uri.yaml new file mode 100644 index 00000000..ce243305 --- /dev/null +++ b/pkg/flatten/testdata/general/fail-nicely-when-no-http-client-provided_uri.yaml @@ -0,0 +1,11 @@ +name: "Fails nicely when no HTTP client provided and uri is used" + +input: + workspace: + components: + - name: test-plugin + plugin: + uri: https://test-repo.io/my-plugino + +output: + errRegexp: "cannot resolve resources by URI: no HTTP client provided" \ No newline at end of file diff --git a/pkg/flatten/testdata/general/fail-nicely-when-no-k8s-client-provided.yaml b/pkg/flatten/testdata/general/fail-nicely-when-no-k8s-client-provided.yaml new file mode 100644 index 00000000..d97adde8 --- /dev/null +++ b/pkg/flatten/testdata/general/fail-nicely-when-no-k8s-client-provided.yaml @@ -0,0 +1,13 @@ +name: "Fails nicely when no Kubernetes client provided" + +input: + workspace: + components: + - name: test-plugin + plugin: + kubernetes: + name: test-plugin-a + namespace: test-ns + +output: + errRegexp: "cannot resolve resources by kubernetes reference: no kubernetes client provided" \ No newline at end of file diff --git a/pkg/flatten/testdata/general/fail-nicely-when-no-namespace.yaml b/pkg/flatten/testdata/general/fail-nicely-when-no-namespace.yaml new file mode 100644 index 00000000..d1d2fbab --- /dev/null +++ b/pkg/flatten/testdata/general/fail-nicely-when-no-namespace.yaml @@ -0,0 +1,12 @@ +name: "Fails nicely when no Kubernetes namespace is provided and there's no default" + +input: + workspace: + components: + - name: test-plugin + plugin: + kubernetes: + name: test-plugin-a + +output: + errRegexp: "specifies a kubernetes reference without namespace and a default is not provided" \ No newline at end of file diff --git a/pkg/flatten/testdata/general/fail-nicely-when-no-registry-url.yaml b/pkg/flatten/testdata/general/fail-nicely-when-no-registry-url.yaml new file mode 100644 index 00000000..7fdf5204 --- /dev/null +++ b/pkg/flatten/testdata/general/fail-nicely-when-no-registry-url.yaml @@ -0,0 +1,11 @@ +name: "Fails nicely when no registry URL is provided and there's no default" + +input: + workspace: + components: + - name: test-plugin + plugin: + id: test/plugin + +output: + errRegexp: "specifies id but has no registryUrl and a default is not provided" \ No newline at end of file From b3956b77b543b84d8ac15742ee43ee343762f143 Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Fri, 19 Mar 2021 19:18:26 -0400 Subject: [PATCH 12/13] Annotate elements merged from parent with their source Add attribute 'library.devfile.io/imported-by: parent' to Components, Commands, Projects, and StarterProjects imported via a parent in the original devfile, to help track where resources in the resolved Devfile came from Signed-off-by: Angel Misevski --- pkg/flatten/annotate.go | 28 ++++++++++++++-------------- pkg/flatten/flatten.go | 3 ++- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/pkg/flatten/annotate.go b/pkg/flatten/annotate.go index 0def15ea..6fa55ab3 100644 --- a/pkg/flatten/annotate.go +++ b/pkg/flatten/annotate.go @@ -9,31 +9,31 @@ const ( ImportSourceAttribute = "library.devfile.io/imported-by" ) -// AddSourceAttributesForPlugin adds an attribute 'library.devfile.io/imported-by=' to all elements of +// AddSourceAttributesForTemplate adds an attribute 'library.devfile.io/imported-by=' to all elements of // a plugin that support attributes. -func AddSourceAttributesForPlugin(sourceID string, plugin *dw.DevWorkspaceTemplateSpec) { - for idx, component := range plugin.Components { +func AddSourceAttributesForTemplate(sourceID string, template *dw.DevWorkspaceTemplateSpec) { + for idx, component := range template.Components { if component.Attributes == nil { - plugin.Components[idx].Attributes = attributes.Attributes{} + template.Components[idx].Attributes = attributes.Attributes{} } - plugin.Components[idx].Attributes.PutString(ImportSourceAttribute, sourceID) + template.Components[idx].Attributes.PutString(ImportSourceAttribute, sourceID) } - for idx, command := range plugin.Commands { + for idx, command := range template.Commands { if command.Attributes == nil { - plugin.Commands[idx].Attributes = attributes.Attributes{} + template.Commands[idx].Attributes = attributes.Attributes{} } - plugin.Commands[idx].Attributes.PutString(ImportSourceAttribute, sourceID) + template.Commands[idx].Attributes.PutString(ImportSourceAttribute, sourceID) } - for idx, project := range plugin.Projects { + for idx, project := range template.Projects { if project.Attributes == nil { - plugin.Projects[idx].Attributes = attributes.Attributes{} + template.Projects[idx].Attributes = attributes.Attributes{} } - plugin.Projects[idx].Attributes.PutString(ImportSourceAttribute, sourceID) + template.Projects[idx].Attributes.PutString(ImportSourceAttribute, sourceID) } - for idx, project := range plugin.StarterProjects { + for idx, project := range template.StarterProjects { if project.Attributes == nil { - plugin.Projects[idx].Attributes = attributes.Attributes{} + template.StarterProjects[idx].Attributes = attributes.Attributes{} } - plugin.Projects[idx].Attributes.PutString(ImportSourceAttribute, sourceID) + template.StarterProjects[idx].Attributes.PutString(ImportSourceAttribute, sourceID) } } diff --git a/pkg/flatten/flatten.go b/pkg/flatten/flatten.go index 6bf8fb48..19a92957 100644 --- a/pkg/flatten/flatten.go +++ b/pkg/flatten/flatten.go @@ -70,6 +70,7 @@ func recursiveResolve(workspace *devfile.DevWorkspaceTemplateSpec, tooling Resol // TODO: implemenent this return nil, fmt.Errorf("parents containing plugins or parents are not supported") } + AddSourceAttributesForTemplate("parent", resolvedParentSpec) resolvedParent = &resolvedParentSpec.DevWorkspaceTemplateSpecContent } @@ -99,7 +100,7 @@ func recursiveResolve(workspace *devfile.DevWorkspaceTemplateSpec, tooling Resol return nil, err } - AddSourceAttributesForPlugin(component.Name, resolvedPlugin) + AddSourceAttributesForTemplate(component.Name, resolvedPlugin) pluginSpecContents = append(pluginSpecContents, &resolvedPlugin.DevWorkspaceTemplateSpecContent) } } From ff01c269414e53a22f8bf00e2ef924cb101292d5 Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Fri, 19 Mar 2021 19:19:55 -0400 Subject: [PATCH 13/13] Add tests to cover attributes on merged devfiles Signed-off-by: Angel Misevski --- pkg/flatten/flatten_test.go | 37 ++++++++++ ...nnotate-devfile-with-importing-source.yaml | 68 +++++++++++++++++++ .../parent/resolve-parent-and-plugins.yaml | 2 + .../testdata/parent/resolve-parent-by-id.yaml | 2 + .../resolve-parent-by-k8s-reference.yaml | 2 + .../parent/resolve-parent-by-uri.yaml | 2 + 6 files changed, 113 insertions(+) create mode 100644 pkg/flatten/testdata/annotate/annotate-devfile-with-importing-source.yaml diff --git a/pkg/flatten/flatten_test.go b/pkg/flatten/flatten_test.go index e284f855..5fc348d3 100644 --- a/pkg/flatten/flatten_test.go +++ b/pkg/flatten/flatten_test.go @@ -217,3 +217,40 @@ func TestResolveDevWorkspaceMissingDefaults(t *testing.T) { }) } } + +func TestResolveDevWorkspaceAnnotations(t *testing.T) { + tests := testutil.LoadAllTestsOrPanic(t, "testdata/annotate") + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + // sanity check: input defines components + assert.True(t, len(tt.Input.Workspace.Components) > 0, "Test case defines workspace with no components") + testHttpGetter := &testutil.FakeHTTPGetter{ + DevfileResources: tt.Input.DevfileResources, + DevWorkspaceResources: tt.Input.DevWorkspaceResources, + Errors: tt.Input.Errors, + } + testK8sClient := &testutil.FakeK8sClient{ + DevWorkspaceResources: tt.Input.DevWorkspaceResources, + Errors: tt.Input.Errors, + } + testResolverTools := ResolverTools{ + Context: context.Background(), + K8sClient: testK8sClient, + HttpClient: testHttpGetter, + DefaultNamespace: "default-namespace", + DefaultRegistryURL: "default-registry-url", + } + outputWorkspace, err := ResolveDevWorkspace(&tt.Input.Workspace, testResolverTools) + if tt.Output.ErrRegexp != nil && assert.Error(t, err) { + assert.Regexp(t, *tt.Output.ErrRegexp, err.Error(), "Error message should match") + } else { + if !assert.NoError(t, err, "Should not return error") { + return + } + assert.Truef(t, cmp.Equal(tt.Output.Workspace, outputWorkspace, testutil.WorkspaceTemplateDiffOpts), + "Workspace should match expected output:\n%s", + cmp.Diff(tt.Output.Workspace, outputWorkspace, testutil.WorkspaceTemplateDiffOpts)) + } + }) + } +} diff --git a/pkg/flatten/testdata/annotate/annotate-devfile-with-importing-source.yaml b/pkg/flatten/testdata/annotate/annotate-devfile-with-importing-source.yaml new file mode 100644 index 00000000..0a36bf82 --- /dev/null +++ b/pkg/flatten/testdata/annotate/annotate-devfile-with-importing-source.yaml @@ -0,0 +1,68 @@ +name: "Annotates resources merged into devfile with source attribute" + +input: + workspace: + parent: + kubernetes: + name: test-parent-k8s + components: + - name: test-plugin + plugin: + uri: https://test-plugin.io/test-plugin + devworkspaceResources: + test-parent-k8s: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: parent-devworkspacetemplate + spec: + projects: + - name: parent-project + starterProjects: + - name: parent-starter-project + components: + - name: parent-component + container: + image: test-img + commands: + - id: parent-command + devfileResources: + "https://test-plugin.io/test-plugin": + schemaVersion: 2.1.0 + metadata: + name: test-plugin + components: + - name: plugin-component + container: + image: plugin-img + commands: + - id: plugin-command +output: + workspace: + projects: + - name: parent-project + attributes: + library.devfile.io/imported-by: parent + starterProjects: + - name: parent-starter-project + attributes: + library.devfile.io/imported-by: parent + components: + - name: parent-component + attributes: + library.devfile.io/imported-by: parent + container: + image: test-img + - name: plugin-component + attributes: + library.devfile.io/imported-by: test-plugin + container: + image: plugin-img + commands: + - id: parent-command + attributes: + library.devfile.io/imported-by: parent + - id: plugin-command + attributes: + library.devfile.io/imported-by: test-plugin + diff --git a/pkg/flatten/testdata/parent/resolve-parent-and-plugins.yaml b/pkg/flatten/testdata/parent/resolve-parent-and-plugins.yaml index 107da0e6..328c6984 100644 --- a/pkg/flatten/testdata/parent/resolve-parent-and-plugins.yaml +++ b/pkg/flatten/testdata/parent/resolve-parent-and-plugins.yaml @@ -49,6 +49,8 @@ output: workspace: components: - name: parent-component + attributes: + library.devfile.io/imported-by: parent container: image: test-img env: diff --git a/pkg/flatten/testdata/parent/resolve-parent-by-id.yaml b/pkg/flatten/testdata/parent/resolve-parent-by-id.yaml index 2ddfbf35..f9f00b62 100644 --- a/pkg/flatten/testdata/parent/resolve-parent-by-id.yaml +++ b/pkg/flatten/testdata/parent/resolve-parent-by-id.yaml @@ -33,6 +33,8 @@ output: workspace: components: - name: parent-component + attributes: + library.devfile.io/imported-by: parent container: image: test-img env: diff --git a/pkg/flatten/testdata/parent/resolve-parent-by-k8s-reference.yaml b/pkg/flatten/testdata/parent/resolve-parent-by-k8s-reference.yaml index 2b7dea41..c9595e4e 100644 --- a/pkg/flatten/testdata/parent/resolve-parent-by-k8s-reference.yaml +++ b/pkg/flatten/testdata/parent/resolve-parent-by-k8s-reference.yaml @@ -35,6 +35,8 @@ output: workspace: components: - name: parent-component + attributes: + library.devfile.io/imported-by: parent container: image: test-img env: diff --git a/pkg/flatten/testdata/parent/resolve-parent-by-uri.yaml b/pkg/flatten/testdata/parent/resolve-parent-by-uri.yaml index e10bc48e..c1bb5a7b 100644 --- a/pkg/flatten/testdata/parent/resolve-parent-by-uri.yaml +++ b/pkg/flatten/testdata/parent/resolve-parent-by-uri.yaml @@ -32,6 +32,8 @@ output: workspace: components: - name: parent-component + attributes: + library.devfile.io/imported-by: parent container: image: test-img env: