Skip to content

Commit 3fa97a1

Browse files
authored
Merge pull request #2104 from fabriziopandini/clusterctl-inspect-images
✨clusterctl: inspect images in provider components
2 parents 1e4af0d + 7f1c28c commit 3fa97a1

File tree

3 files changed

+171
-2
lines changed

3 files changed

+171
-2
lines changed

cmd/clusterctl/cmd/config_providers.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,14 @@ func componentsDefaultOutput(c client.Components) error {
147147
fmt.Printf(" %s\n", v)
148148
}
149149
}
150+
if len(c.Images()) > 0 {
151+
fmt.Println("Images:")
152+
fmt.Println(" Name")
153+
fmt.Println(" ----")
154+
for _, v := range c.Images() {
155+
fmt.Printf(" %s\n", v)
156+
}
157+
}
150158

151159
return nil
152160
}

cmd/clusterctl/pkg/client/repository/components.go

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,20 @@ var variableRegEx = regexp.MustCompile(`\${\s*([A-Z0-9_]+)\s*}`)
4747
// 4. Set the watching namespace for the provider controller
4848
// 5. Adds labels to all the components in order to allow easy identification of the provider objects
4949
type Components interface {
50-
// configuration of the provider the template belongs to.
50+
// configuration of the provider the provider components belongs to.
5151
config.Provider
5252

5353
// Version of the provider.
5454
Version() string
5555

56-
// Variables required by the template.
56+
// Variables required by the provider components.
5757
// This value is derived by the component YAML.
5858
Variables() []string
5959

60+
// Images required to install the provider components.
61+
// This value is derived by the component YAML.
62+
Images() []string
63+
6064
// TargetNamespace where the provider components will be installed.
6165
// By default this value is derived by the component YAML, but it is possible to override it
6266
// during the creation of the Components object.
@@ -83,6 +87,7 @@ type components struct {
8387
config.Provider
8488
version string
8589
variables []string
90+
images []string
8691
targetNamespace string
8792
watchingNamespace string
8893
objs []unstructured.Unstructured
@@ -99,6 +104,10 @@ func (c *components) Variables() []string {
99104
return c.variables
100105
}
101106

107+
func (c *components) Images() []string {
108+
return c.images
109+
}
110+
102111
func (c *components) TargetNamespace() string {
103112
return c.targetNamespace
104113
}
@@ -161,6 +170,12 @@ func newComponents(provider config.Provider, version string, rawyaml []byte, con
161170
return nil, errors.Wrap(err, "failed to parse yaml")
162171
}
163172

173+
// inspect the list of objects for the images required by the provider component
174+
images, err := inspectImages(objs)
175+
if err != nil {
176+
return nil, errors.Wrap(err, "failed to detect required images")
177+
}
178+
164179
// inspect the list of objects for the default target namespace
165180
// the default target namespace is the namespace object defined in the component yaml read from the repository, if any
166181
defaultTargetNamespace, err := inspectTargetNamespace(objs)
@@ -215,6 +230,7 @@ func newComponents(provider config.Provider, version string, rawyaml []byte, con
215230
Provider: provider,
216231
version: version,
217232
variables: variables,
233+
images: images,
218234
targetNamespace: targetNamespace,
219235
watchingNamespace: watchingNamespace,
220236
objs: objs,
@@ -522,6 +538,32 @@ func remove(slice []string, i int) []string {
522538
return slice[:len(slice)-1]
523539
}
524540

541+
// inspectImages identifies the container images required to install the provider.
542+
// NB. The implemented approach is specific for the provider components YAML; it is not intended to cover
543+
// all the possible objects used to deploy containers existing in Kubernetes.
544+
func inspectImages(objs []unstructured.Unstructured) ([]string, error) {
545+
images := []string{}
546+
547+
for _, o := range objs {
548+
if o.GetKind() == deploymentKind {
549+
d := &appsv1.Deployment{}
550+
if err := scheme.Scheme.Convert(&o, d, nil); err != nil { //nolint
551+
return nil, err
552+
}
553+
554+
for _, c := range d.Spec.Template.Spec.Containers {
555+
images = append(images, c.Image)
556+
}
557+
558+
for _, c := range d.Spec.Template.Spec.InitContainers {
559+
images = append(images, c.Image)
560+
}
561+
}
562+
}
563+
564+
return images, nil
565+
}
566+
525567
// addLabels ensures all the provider components have a consistent set of labels
526568
func addLabels(objs []unstructured.Unstructured, name string) []unstructured.Unstructured {
527569
for _, o := range objs {

cmd/clusterctl/pkg/client/repository/components_test.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,125 @@ func Test_fixWatchNamespace(t *testing.T) {
737737
}
738738
}
739739

740+
func Test_inspectImages(t *testing.T) {
741+
type args struct {
742+
objs []unstructured.Unstructured
743+
}
744+
tests := []struct {
745+
name string
746+
args args
747+
want []string
748+
wantErr bool
749+
}{
750+
{
751+
name: "controller without the RBAC proxy",
752+
args: args{
753+
objs: []unstructured.Unstructured{
754+
{
755+
Object: map[string]interface{}{
756+
"apiVersion": "apps/v1",
757+
"kind": deploymentKind,
758+
"spec": map[string]interface{}{
759+
"template": map[string]interface{}{
760+
"spec": map[string]interface{}{
761+
"containers": []map[string]interface{}{
762+
{
763+
"name": controllerContainerName,
764+
"image": "gcr.io/k8s-staging-cluster-api/cluster-api-controller:master",
765+
},
766+
},
767+
},
768+
},
769+
},
770+
},
771+
},
772+
},
773+
},
774+
want: []string{"gcr.io/k8s-staging-cluster-api/cluster-api-controller:master"},
775+
wantErr: false,
776+
},
777+
{
778+
name: "controller with the RBAC proxy",
779+
args: args{
780+
objs: []unstructured.Unstructured{
781+
{
782+
Object: map[string]interface{}{
783+
"apiVersion": "apps/v1",
784+
"kind": deploymentKind,
785+
"spec": map[string]interface{}{
786+
"template": map[string]interface{}{
787+
"spec": map[string]interface{}{
788+
"containers": []map[string]interface{}{
789+
{
790+
"name": controllerContainerName,
791+
"image": "gcr.io/k8s-staging-cluster-api/cluster-api-controller:master",
792+
},
793+
{
794+
"name": "kube-rbac-proxy",
795+
"image": "gcr.io/kubebuilder/kube-rbac-proxy:v0.4.1",
796+
},
797+
},
798+
},
799+
},
800+
},
801+
},
802+
},
803+
},
804+
},
805+
want: []string{"gcr.io/k8s-staging-cluster-api/cluster-api-controller:master", "gcr.io/kubebuilder/kube-rbac-proxy:v0.4.1"},
806+
wantErr: false,
807+
},
808+
{
809+
name: "controller with init container",
810+
args: args{
811+
objs: []unstructured.Unstructured{
812+
{
813+
Object: map[string]interface{}{
814+
"apiVersion": "apps/v1",
815+
"kind": deploymentKind,
816+
"spec": map[string]interface{}{
817+
"template": map[string]interface{}{
818+
"spec": map[string]interface{}{
819+
"containers": []map[string]interface{}{
820+
{
821+
"name": controllerContainerName,
822+
"image": "gcr.io/k8s-staging-cluster-api/cluster-api-controller:master",
823+
},
824+
},
825+
"initContainers": []map[string]interface{}{
826+
{
827+
"name": controllerContainerName,
828+
"image": "gcr.io/k8s-staging-cluster-api/cluster-api-controller:init",
829+
},
830+
},
831+
},
832+
},
833+
},
834+
},
835+
},
836+
},
837+
},
838+
want: []string{"gcr.io/k8s-staging-cluster-api/cluster-api-controller:master", "gcr.io/k8s-staging-cluster-api/cluster-api-controller:init"},
839+
wantErr: false,
840+
},
841+
}
842+
for _, tt := range tests {
843+
t.Run(tt.name, func(t *testing.T) {
844+
got, err := inspectImages(tt.args.objs)
845+
if (err != nil) != tt.wantErr {
846+
t.Fatalf("error = %v, wantErr %v", err, tt.wantErr)
847+
}
848+
if tt.wantErr {
849+
return
850+
}
851+
852+
if !reflect.DeepEqual(got, tt.want) {
853+
t.Errorf("got = %v, want %v", got, tt.want)
854+
}
855+
})
856+
}
857+
}
858+
740859
func Test_addLabels(t *testing.T) {
741860
type args struct {
742861
objs []unstructured.Unstructured

0 commit comments

Comments
 (0)