diff --git a/cmd/controller/run.go b/cmd/controller/run.go index 0977d164f..568b3a385 100644 --- a/cmd/controller/run.go +++ b/cmd/controller/run.go @@ -17,9 +17,10 @@ import ( pkgclient "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apiserver/client/clientset/versioned" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/app" kcclient "github.com/vmware-tanzu/carvel-kapp-controller/pkg/client/clientset/versioned" + "github.com/vmware-tanzu/carvel-kapp-controller/pkg/componentinfo" kcconfig "github.com/vmware-tanzu/carvel-kapp-controller/pkg/config" - "github.com/vmware-tanzu/carvel-kapp-controller/pkg/deploy" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/exec" + "github.com/vmware-tanzu/carvel-kapp-controller/pkg/kubeconfig" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/metrics" pkginstall "github.com/vmware-tanzu/carvel-kapp-controller/pkg/packageinstall" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/pkgrepository" @@ -170,20 +171,22 @@ func Run(opts Options, runLog logr.Logger) error { refTracker := reftracker.NewAppRefTracker() updateStatusTracker := reftracker.NewAppUpdateStatus() - // initialize deploy factory once - the deploy factory contains a service account token cache which should be only setup once. - deployFactory := deploy.NewFactory(coreClient, kcConfig, sidecarCmdExec, runLog) + // initialize cluster access once - it contains a service account token cache which should be only setup once. + kubeconf := kubeconfig.NewKubeconfig(coreClient, runLog) + compInfo := componentinfo.NewComponentInfo(coreClient, kubeconf, Version) { // add controller for apps appFactory := app.CRDAppFactory{ - CoreClient: coreClient, - AppClient: kcClient, - KcConfig: kcConfig, - AppMetrics: appMetrics, - CmdRunner: sidecarCmdExec, - DeployFactory: deployFactory, + CoreClient: coreClient, + AppClient: kcClient, + KcConfig: kcConfig, + AppMetrics: appMetrics, + CmdRunner: sidecarCmdExec, + Kubeconf: kubeconf, + CompInfo: compInfo, } reconciler := app.NewReconciler(kcClient, runLog.WithName("app"), - appFactory, refTracker, updateStatusTracker) + appFactory, refTracker, updateStatusTracker, compInfo) ctrl, err := controller.New("app", mgr, controller.Options{ Reconciler: NewUniqueReconciler(&ErrReconciler{ @@ -206,7 +209,7 @@ func Run(opts Options, runLog logr.Logger) error { pkgToPkgInstallHandler := pkginstall.NewPackageInstallVersionHandler( kcClient, opts.PackagingGloablNS, runLog.WithName("handler")) - reconciler := pkginstall.NewReconciler(deployFactory, kcClient, pkgClient, coreClient, pkgToPkgInstallHandler, runLog.WithName("pkgi"), Version) + reconciler := pkginstall.NewReconciler(kcClient, pkgClient, coreClient, pkgToPkgInstallHandler, runLog.WithName("pkgi"), compInfo) ctrl, err := controller.New("pkgi", mgr, controller.Options{ Reconciler: reconciler, @@ -223,7 +226,7 @@ func Run(opts Options, runLog logr.Logger) error { } { // add controller for pkgrepositories - appFactory := pkgrepository.AppFactory{coreClient, kcClient, kcConfig, sidecarCmdExec} + appFactory := pkgrepository.AppFactory{coreClient, kcClient, kcConfig, sidecarCmdExec, kubeconf} reconciler := pkgrepository.NewReconciler(kcClient, coreClient, runLog.WithName("pkgr"), appFactory, refTracker, updateStatusTracker) diff --git a/config/crds.yml b/config/crds.yml index 69fe51b1f..5848883c5 100644 --- a/config/crds.yml +++ b/config/crds.yml @@ -427,6 +427,26 @@ spec: fieldPath: description: 'Required: Selects a field of the app: only annotations, labels, uid, name and namespace are supported.' type: string + kappControllerVersion: + description: 'Optional: Get running KappController version, defaults (empty) to retrieving the current running version.. Can be manually supplied instead.' + properties: + version: + type: string + type: object + kubernetesAPIs: + description: 'Optional: Get running KubernetesAPIs from cluster, defaults (empty) to retrieving the APIs from the cluster. Can be manually supplied instead, e.g ["group/version", "group2/version2"]' + properties: + groupVersions: + items: + type: string + type: array + type: object + kubernetesVersion: + description: 'Optional: Get running Kubernetes version from cluster, defaults (empty) to retrieving the version from the cluster. Can be manually supplied instead.' + properties: + version: + type: string + type: object name: type: string type: object @@ -445,6 +465,20 @@ spec: helmTemplate: description: Use helm template command to render helm chart properties: + kubernetesAPIs: + description: 'Optional: Use kubernetes group/versions resources available in the live cluster' + properties: + groupVersions: + items: + type: string + type: array + type: object + kubernetesVersion: + description: 'Optional: Get Kubernetes version, defaults (empty) to retrieving the version from the cluster. Can be manually overridden to a value instead.' + properties: + version: + type: string + type: object name: description: Set name explicitly, default is App CR's name (optional; v0.13.0+) type: string @@ -471,6 +505,26 @@ spec: fieldPath: description: 'Required: Selects a field of the app: only annotations, labels, uid, name and namespace are supported.' type: string + kappControllerVersion: + description: 'Optional: Get running KappController version, defaults (empty) to retrieving the current running version.. Can be manually supplied instead.' + properties: + version: + type: string + type: object + kubernetesAPIs: + description: 'Optional: Get running KubernetesAPIs from cluster, defaults (empty) to retrieving the APIs from the cluster. Can be manually supplied instead, e.g ["group/version", "group2/version2"]' + properties: + groupVersions: + items: + type: string + type: array + type: object + kubernetesVersion: + description: 'Optional: Get running Kubernetes version from cluster, defaults (empty) to retrieving the version from the cluster. Can be manually supplied instead.' + properties: + version: + type: string + type: object name: type: string type: object @@ -595,6 +649,26 @@ spec: fieldPath: description: 'Required: Selects a field of the app: only annotations, labels, uid, name and namespace are supported.' type: string + kappControllerVersion: + description: 'Optional: Get running KappController version, defaults (empty) to retrieving the current running version.. Can be manually supplied instead.' + properties: + version: + type: string + type: object + kubernetesAPIs: + description: 'Optional: Get running KubernetesAPIs from cluster, defaults (empty) to retrieving the APIs from the cluster. Can be manually supplied instead, e.g ["group/version", "group2/version2"]' + properties: + groupVersions: + items: + type: string + type: array + type: object + kubernetesVersion: + description: 'Optional: Get running Kubernetes version from cluster, defaults (empty) to retrieving the version from the cluster. Can be manually supplied instead.' + properties: + version: + type: string + type: object name: type: string type: object @@ -966,6 +1040,26 @@ spec: fieldPath: description: 'Required: Selects a field of the app: only annotations, labels, uid, name and namespace are supported.' type: string + kappControllerVersion: + description: 'Optional: Get running KappController version, defaults (empty) to retrieving the current running version.. Can be manually supplied instead.' + properties: + version: + type: string + type: object + kubernetesAPIs: + description: 'Optional: Get running KubernetesAPIs from cluster, defaults (empty) to retrieving the APIs from the cluster. Can be manually supplied instead, e.g ["group/version", "group2/version2"]' + properties: + groupVersions: + items: + type: string + type: array + type: object + kubernetesVersion: + description: 'Optional: Get running Kubernetes version from cluster, defaults (empty) to retrieving the version from the cluster. Can be manually supplied instead.' + properties: + version: + type: string + type: object name: type: string type: object @@ -984,6 +1078,20 @@ spec: helmTemplate: description: Use helm template command to render helm chart properties: + kubernetesAPIs: + description: 'Optional: Use kubernetes group/versions resources available in the live cluster' + properties: + groupVersions: + items: + type: string + type: array + type: object + kubernetesVersion: + description: 'Optional: Get Kubernetes version, defaults (empty) to retrieving the version from the cluster. Can be manually overridden to a value instead.' + properties: + version: + type: string + type: object name: description: Set name explicitly, default is App CR's name (optional; v0.13.0+) type: string @@ -1010,6 +1118,26 @@ spec: fieldPath: description: 'Required: Selects a field of the app: only annotations, labels, uid, name and namespace are supported.' type: string + kappControllerVersion: + description: 'Optional: Get running KappController version, defaults (empty) to retrieving the current running version.. Can be manually supplied instead.' + properties: + version: + type: string + type: object + kubernetesAPIs: + description: 'Optional: Get running KubernetesAPIs from cluster, defaults (empty) to retrieving the APIs from the cluster. Can be manually supplied instead, e.g ["group/version", "group2/version2"]' + properties: + groupVersions: + items: + type: string + type: array + type: object + kubernetesVersion: + description: 'Optional: Get running Kubernetes version from cluster, defaults (empty) to retrieving the version from the cluster. Can be manually supplied instead.' + properties: + version: + type: string + type: object name: type: string type: object @@ -1134,6 +1262,26 @@ spec: fieldPath: description: 'Required: Selects a field of the app: only annotations, labels, uid, name and namespace are supported.' type: string + kappControllerVersion: + description: 'Optional: Get running KappController version, defaults (empty) to retrieving the current running version.. Can be manually supplied instead.' + properties: + version: + type: string + type: object + kubernetesAPIs: + description: 'Optional: Get running KubernetesAPIs from cluster, defaults (empty) to retrieving the APIs from the cluster. Can be manually supplied instead, e.g ["group/version", "group2/version2"]' + properties: + groupVersions: + items: + type: string + type: array + type: object + kubernetesVersion: + description: 'Optional: Get running Kubernetes version from cluster, defaults (empty) to retrieving the version from the cluster. Can be manually supplied instead.' + properties: + version: + type: string + type: object name: type: string type: object diff --git a/pkg/apis/kappctrl/v1alpha1/generated.pb.go b/pkg/apis/kappctrl/v1alpha1/generated.pb.go index a95e658f2..9730624e5 100644 --- a/pkg/apis/kappctrl/v1alpha1/generated.pb.go +++ b/pkg/apis/kappctrl/v1alpha1/generated.pb.go @@ -1011,6 +1011,62 @@ func (m *GenericStatus) XXX_DiscardUnknown() { var xxx_messageInfo_GenericStatus proto.InternalMessageInfo +func (m *KubernetesAPIs) Reset() { *m = KubernetesAPIs{} } +func (*KubernetesAPIs) ProtoMessage() {} +func (*KubernetesAPIs) Descriptor() ([]byte, []int) { + return fileDescriptor_e972ccf085273df7, []int{35} +} +func (m *KubernetesAPIs) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *KubernetesAPIs) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *KubernetesAPIs) XXX_Merge(src proto.Message) { + xxx_messageInfo_KubernetesAPIs.Merge(m, src) +} +func (m *KubernetesAPIs) XXX_Size() int { + return m.Size() +} +func (m *KubernetesAPIs) XXX_DiscardUnknown() { + xxx_messageInfo_KubernetesAPIs.DiscardUnknown(m) +} + +var xxx_messageInfo_KubernetesAPIs proto.InternalMessageInfo + +func (m *Version) Reset() { *m = Version{} } +func (*Version) ProtoMessage() {} +func (*Version) Descriptor() ([]byte, []int) { + return fileDescriptor_e972ccf085273df7, []int{36} +} +func (m *Version) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Version) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *Version) XXX_Merge(src proto.Message) { + xxx_messageInfo_Version.Merge(m, src) +} +func (m *Version) XXX_Size() int { + return m.Size() +} +func (m *Version) XXX_DiscardUnknown() { + xxx_messageInfo_Version.DiscardUnknown(m) +} + +var xxx_messageInfo_Version proto.InternalMessageInfo + func init() { proto.RegisterType((*AppCluster)(nil), "github.com.vmware_tanzu.carvel_kapp_controller.pkg.apis.kappctrl.v1alpha1.AppCluster") proto.RegisterType((*AppClusterKubeconfigSecretRef)(nil), "github.com.vmware_tanzu.carvel_kapp_controller.pkg.apis.kappctrl.v1alpha1.AppClusterKubeconfigSecretRef") @@ -1048,6 +1104,8 @@ func init() { proto.RegisterType((*AppTemplateYtt)(nil), "github.com.vmware_tanzu.carvel_kapp_controller.pkg.apis.kappctrl.v1alpha1.AppTemplateYtt") proto.RegisterType((*Condition)(nil), "github.com.vmware_tanzu.carvel_kapp_controller.pkg.apis.kappctrl.v1alpha1.Condition") proto.RegisterType((*GenericStatus)(nil), "github.com.vmware_tanzu.carvel_kapp_controller.pkg.apis.kappctrl.v1alpha1.GenericStatus") + proto.RegisterType((*KubernetesAPIs)(nil), "github.com.vmware_tanzu.carvel_kapp_controller.pkg.apis.kappctrl.v1alpha1.KubernetesAPIs") + proto.RegisterType((*Version)(nil), "github.com.vmware_tanzu.carvel_kapp_controller.pkg.apis.kappctrl.v1alpha1.Version") } func init() { @@ -1055,157 +1113,166 @@ func init() { } var fileDescriptor_e972ccf085273df7 = []byte{ - // 2389 bytes of a gzipped FileDescriptorProto + // 2533 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x5a, 0x4d, 0x6c, 0x1b, 0xc7, - 0x15, 0xf6, 0x92, 0x94, 0x48, 0x3e, 0x49, 0xb6, 0x32, 0xb6, 0x6b, 0x42, 0xad, 0x45, 0x63, 0x03, - 0x04, 0x36, 0x5a, 0x93, 0xb0, 0xe0, 0x18, 0x46, 0x0b, 0x14, 0x10, 0x25, 0x4b, 0x96, 0x6d, 0xd9, - 0xc4, 0x50, 0x76, 0xe3, 0xb4, 0x89, 0xb3, 0x5a, 0x8e, 0xc8, 0x2d, 0xf7, 0xaf, 0xbb, 0xb3, 0xb4, - 0x18, 0xa4, 0x40, 0xff, 0x50, 0xa4, 0x4d, 0x81, 0x06, 0x05, 0x1a, 0x14, 0x2d, 0xd0, 0x63, 0x7b, - 0x2e, 0xda, 0x63, 0x2e, 0x45, 0x2f, 0x06, 0x7a, 0x09, 0x90, 0x4b, 0x4e, 0x6c, 0xcd, 0x9e, 0x7a, - 0x2c, 0x72, 0xcb, 0xa9, 0x98, 0x9f, 0xfd, 0x13, 0x29, 0x53, 0x05, 0xb4, 0x8e, 0x03, 0xf4, 0xb6, - 0x33, 0xf3, 0xe6, 0x7d, 0x6f, 0xde, 0xbc, 0xf7, 0xe6, 0xbd, 0x47, 0xc2, 0xc3, 0x8e, 0x41, 0xbb, - 0xc1, 0x6e, 0x4d, 0x77, 0xac, 0x7a, 0xdf, 0x7a, 0xac, 0x79, 0xe4, 0x32, 0xd5, 0xec, 0xb7, 0x83, - 0xba, 0xae, 0x79, 0x7d, 0x62, 0x5e, 0xee, 0x69, 0xae, 0x7b, 0x59, 0x77, 0x6c, 0xea, 0x39, 0xa6, - 0x49, 0xbc, 0xba, 0xdb, 0xeb, 0xd4, 0x35, 0xd7, 0xf0, 0xeb, 0x6c, 0x41, 0xa7, 0x9e, 0x59, 0xef, - 0x5f, 0xd1, 0x4c, 0xb7, 0xab, 0x5d, 0xa9, 0x77, 0x88, 0x4d, 0x3c, 0x8d, 0x92, 0x76, 0xcd, 0xf5, - 0x1c, 0xea, 0xa0, 0xad, 0x98, 0x75, 0x4d, 0xb0, 0x7e, 0xc4, 0x59, 0xd7, 0x04, 0xeb, 0x47, 0x8c, - 0xc3, 0xa3, 0x98, 0x75, 0xcd, 0xed, 0x75, 0x6a, 0x8c, 0x75, 0x2d, 0x64, 0x5d, 0x0b, 0x59, 0x2f, - 0x5d, 0x4e, 0x48, 0xd9, 0x71, 0x3a, 0x4e, 0x9d, 0x23, 0xec, 0x06, 0x7b, 0x7c, 0xc4, 0x07, 0xfc, - 0x4b, 0x20, 0x2f, 0xe1, 0x29, 0x87, 0xea, 0x13, 0xbb, 0x6d, 0x88, 0xb3, 0xc8, 0xcf, 0x3e, 0xf1, - 0x7c, 0xc3, 0xb1, 0xfd, 0x43, 0x4f, 0xb3, 0x74, 0xb5, 0x77, 0xdd, 0xaf, 0x19, 0x0e, 0x3b, 0xbc, - 0xa5, 0xe9, 0x5d, 0xc3, 0x26, 0xde, 0x20, 0xd6, 0x86, 0x45, 0xa8, 0x56, 0xef, 0x8f, 0xef, 0xaa, - 0x1f, 0xb6, 0xcb, 0x0b, 0x6c, 0x6a, 0x58, 0x64, 0x6c, 0xc3, 0xb5, 0x69, 0x1b, 0x7c, 0xbd, 0x4b, - 0x2c, 0xed, 0xe0, 0x3e, 0xf5, 0x53, 0x05, 0x60, 0xd5, 0x75, 0xd7, 0xcc, 0xc0, 0xa7, 0xc4, 0x43, - 0x75, 0x28, 0xdb, 0x9a, 0x45, 0x7c, 0x57, 0xd3, 0x49, 0x45, 0xb9, 0xa0, 0x5c, 0x2c, 0x37, 0x5e, - 0x7a, 0x32, 0xac, 0x9e, 0x18, 0x0d, 0xab, 0xe5, 0xbb, 0xe1, 0x02, 0x8e, 0x69, 0xd0, 0x9f, 0x15, - 0x38, 0xdd, 0x0b, 0x76, 0x89, 0xee, 0xd8, 0x7b, 0x46, 0xa7, 0x45, 0x74, 0x8f, 0x50, 0x4c, 0xf6, - 0x2a, 0xb9, 0x0b, 0xca, 0xc5, 0xb9, 0x95, 0x6e, 0xed, 0xd8, 0xee, 0xb2, 0x16, 0x4b, 0x79, 0x7b, - 0x1c, 0xaf, 0x71, 0x6e, 0x34, 0xac, 0x9e, 0x9e, 0xb0, 0x80, 0x27, 0x49, 0xa7, 0xbe, 0x05, 0xe7, - 0x9f, 0xc9, 0x0e, 0x5d, 0x80, 0x02, 0x3b, 0xa3, 0x54, 0xc1, 0xbc, 0x54, 0x41, 0x81, 0xa9, 0x00, - 0xf3, 0x15, 0x74, 0x1e, 0xf2, 0x3d, 0x32, 0xe0, 0xe7, 0x2c, 0x37, 0xe6, 0x24, 0x41, 0xfe, 0x36, - 0x19, 0x60, 0x36, 0xaf, 0xfe, 0x58, 0x81, 0xf2, 0xaa, 0xeb, 0xae, 0x13, 0xd7, 0x74, 0x06, 0xa8, - 0x0f, 0x05, 0x76, 0x20, 0xce, 0x6e, 0x6e, 0xe5, 0xb5, 0xe3, 0xd5, 0x8a, 0xc0, 0xb8, 0xad, 0xb9, - 0x6e, 0xa3, 0xc4, 0x84, 0x64, 0x5f, 0x98, 0xe3, 0xa9, 0x1f, 0xe4, 0x61, 0x21, 0x45, 0x81, 0x5e, - 0x81, 0x59, 0xc3, 0xa6, 0xce, 0x5d, 0x5f, 0x1e, 0xed, 0xa4, 0x94, 0x7c, 0x76, 0x8b, 0xcf, 0x62, - 0xb9, 0x8a, 0xaa, 0x30, 0x63, 0x69, 0xee, 0x5d, 0xbf, 0x92, 0xbb, 0x90, 0xbf, 0x58, 0x6e, 0x94, - 0x47, 0xc3, 0xea, 0xcc, 0x36, 0x9b, 0xc0, 0x62, 0x1e, 0xd5, 0x00, 0x3c, 0xed, 0xf1, 0x3d, 0x97, - 0x32, 0xf3, 0xaf, 0xe4, 0x39, 0xd5, 0xc9, 0xd1, 0xb0, 0x0a, 0x38, 0x9a, 0xc5, 0x09, 0x0a, 0xf4, - 0x53, 0x05, 0x8a, 0x86, 0xed, 0xbb, 0x44, 0xa7, 0x95, 0x02, 0x57, 0xc3, 0xa3, 0xac, 0xd4, 0xb0, - 0x25, 0x60, 0x1a, 0x73, 0xa3, 0x61, 0xb5, 0x28, 0x07, 0x38, 0x04, 0x47, 0x3f, 0x52, 0x60, 0xb6, - 0x4d, 0x4c, 0x42, 0x49, 0x65, 0x86, 0xcb, 0xf1, 0x66, 0x56, 0x72, 0xac, 0x73, 0x94, 0x06, 0x30, - 0xf5, 0x8a, 0x6f, 0x2c, 0x91, 0xd5, 0x1b, 0x70, 0x7a, 0x02, 0xe9, 0x01, 0xa5, 0x2a, 0xd3, 0x94, - 0xaa, 0x6e, 0xc0, 0x99, 0x49, 0x27, 0xff, 0x9f, 0xf9, 0xfc, 0x65, 0x16, 0x4a, 0xab, 0xae, 0xbb, - 0x41, 0xa8, 0xde, 0x45, 0xdf, 0x67, 0x26, 0x62, 0x1a, 0x36, 0x91, 0xe6, 0xfa, 0xf0, 0x78, 0xf5, - 0xc3, 0x41, 0xb6, 0x38, 0x80, 0x50, 0x8d, 0xf8, 0xc6, 0x12, 0x14, 0x0d, 0x60, 0xc6, 0xb0, 0xb4, - 0x0e, 0x91, 0x21, 0xe4, 0xb5, 0x2c, 0xd0, 0x19, 0x7f, 0x61, 0xd3, 0xfc, 0x13, 0x0b, 0x44, 0x14, - 0x40, 0xa1, 0x4b, 0xa9, 0x5b, 0xc9, 0x73, 0xe4, 0x6f, 0x65, 0x80, 0x7c, 0x73, 0x67, 0xa7, 0x29, - 0xbc, 0x94, 0x7d, 0x61, 0x0e, 0x87, 0xbe, 0x07, 0xf9, 0x8e, 0x11, 0x7a, 0xc5, 0x83, 0x0c, 0x50, - 0x37, 0x0d, 0xda, 0x28, 0xb2, 0xf0, 0xb4, 0x69, 0x50, 0xcc, 0xb0, 0xd0, 0xcf, 0x14, 0x28, 0x77, - 0x89, 0x69, 0xad, 0x75, 0x35, 0x8f, 0x4a, 0x3f, 0xf8, 0x4e, 0x16, 0xe7, 0x0d, 0x31, 0x1a, 0x0b, - 0xec, 0x09, 0x89, 0x86, 0x38, 0x46, 0x47, 0xbf, 0x56, 0x60, 0xde, 0xb0, 0x3a, 0x6e, 0xaf, 0xd3, - 0x08, 0xec, 0xb6, 0x49, 0x2a, 0xb3, 0x59, 0x84, 0x07, 0x79, 0xf1, 0x31, 0x4c, 0x63, 0x71, 0x34, - 0xac, 0xce, 0x27, 0x67, 0x70, 0x4a, 0x0c, 0xf6, 0x06, 0xb8, 0x1a, 0xed, 0x56, 0x8a, 0xe9, 0x37, - 0xa0, 0xa9, 0xd1, 0x2e, 0xe6, 0x2b, 0xea, 0xdf, 0xf3, 0x30, 0x97, 0xd0, 0x31, 0x7b, 0x13, 0x02, - 0xcf, 0x94, 0x91, 0x35, 0x7a, 0x13, 0xee, 0xe3, 0x3b, 0x98, 0xcd, 0xb3, 0x65, 0x4f, 0x3e, 0x8d, - 0x89, 0x65, 0xf6, 0x48, 0xb1, 0x79, 0xf4, 0x73, 0x05, 0xe6, 0x3d, 0xb2, 0xd7, 0x22, 0x26, 0xd1, - 0x99, 0x5b, 0x4a, 0x3d, 0xec, 0x4c, 0xd3, 0x83, 0x48, 0x45, 0xf8, 0xf1, 0xe5, 0x67, 0x98, 0x95, - 0xc4, 0x0a, 0x78, 0x20, 0x66, 0x22, 0xde, 0xe2, 0xf0, 0x38, 0x81, 0x86, 0x53, 0xd8, 0xe8, 0x5d, - 0x05, 0xca, 0x7e, 0xf4, 0x9a, 0x0b, 0x87, 0xf8, 0x76, 0x06, 0x37, 0x72, 0xc7, 0xd1, 0x35, 0x93, - 0x3d, 0xe0, 0xdc, 0x3e, 0xe2, 0x67, 0x3b, 0x06, 0x47, 0x97, 0xa0, 0xe8, 0x07, 0xbb, 0x4c, 0xed, - 0xdc, 0x45, 0xca, 0x8d, 0x53, 0x52, 0x75, 0xc5, 0x96, 0x98, 0xc6, 0xe1, 0x3a, 0xfa, 0x06, 0x2c, - 0x98, 0x7b, 0x7e, 0xab, 0x67, 0xb8, 0x2d, 0x2b, 0x68, 0x77, 0x44, 0x84, 0x2f, 0x35, 0xce, 0xca, - 0x0d, 0x0b, 0x77, 0x36, 0x5a, 0xf1, 0x22, 0x4e, 0xd3, 0xaa, 0xef, 0xe5, 0x60, 0x3e, 0xe9, 0xa7, - 0xd3, 0xae, 0xf3, 0x15, 0x98, 0xf5, 0xbb, 0xda, 0xca, 0xab, 0xd7, 0xe4, 0x8d, 0x46, 0x4f, 0x69, - 0xeb, 0xe6, 0xea, 0xca, 0xab, 0xd7, 0xb0, 0x5c, 0xfd, 0x62, 0xaa, 0x92, 0x25, 0x86, 0x2f, 0x8d, - 0x79, 0xf1, 0x11, 0xf2, 0xa2, 0x4b, 0x50, 0x94, 0xe6, 0x27, 0xd5, 0x12, 0x41, 0x48, 0x1b, 0xc4, - 0xe1, 0x3a, 0x7a, 0x5f, 0x01, 0xf0, 0x88, 0xeb, 0xf8, 0x06, 0x75, 0xbc, 0x81, 0xd4, 0xcc, 0x5b, - 0x59, 0x46, 0x21, 0x4c, 0x5c, 0x47, 0x3e, 0x84, 0x11, 0x2e, 0x4e, 0xc8, 0xa0, 0xfe, 0x55, 0x81, - 0xb3, 0x13, 0x77, 0x4d, 0x33, 0x86, 0xf4, 0x25, 0xe7, 0x3e, 0xc7, 0x4b, 0x56, 0xff, 0x93, 0xe3, - 0x49, 0x5f, 0xfc, 0xd2, 0x4d, 0x93, 0x9d, 0x05, 0x1e, 0xaa, 0x75, 0xe2, 0xc0, 0x53, 0xc8, 0x3a, - 0xf0, 0xec, 0x24, 0xd0, 0x70, 0x0a, 0xfb, 0x05, 0x52, 0x64, 0xd2, 0x5b, 0xf2, 0x53, 0xbc, 0xe5, - 0xe3, 0x1c, 0xcf, 0xc4, 0xc6, 0x1e, 0x19, 0xf4, 0x72, 0x98, 0xcd, 0x08, 0xe5, 0x2f, 0x48, 0x0e, - 0xe9, 0xbc, 0x63, 0xec, 0x02, 0xf2, 0xff, 0xbf, 0x00, 0x66, 0xc9, 0xff, 0xce, 0xc1, 0xc9, 0x74, - 0xc6, 0xc8, 0x12, 0x97, 0x19, 0xf6, 0xf6, 0x8a, 0xac, 0x76, 0x6e, 0xa5, 0x9d, 0x59, 0x72, 0x5a, - 0x63, 0x17, 0xeb, 0xdf, 0xb0, 0xa9, 0x37, 0x88, 0xaf, 0x8d, 0xcf, 0x61, 0x21, 0x01, 0x8b, 0x5f, - 0x65, 0xfe, 0xb5, 0xe1, 0x39, 0x16, 0x2f, 0x94, 0x32, 0xca, 0x5a, 0xb8, 0x3c, 0x2d, 0x27, 0xf0, - 0x74, 0x12, 0x97, 0xe3, 0xcd, 0x10, 0x19, 0xc7, 0x42, 0x2c, 0x5d, 0x07, 0x88, 0xc5, 0x46, 0x8b, - 0xa2, 0x46, 0xe5, 0xa6, 0xc7, 0xcb, 0x52, 0x74, 0x06, 0x66, 0xfa, 0x9a, 0x19, 0x88, 0xe4, 0xba, - 0x8c, 0xc5, 0xe0, 0xeb, 0xb9, 0xeb, 0x8a, 0xfa, 0xb7, 0xa4, 0x05, 0x27, 0x00, 0xd1, 0x2f, 0x53, - 0xf6, 0x20, 0x4a, 0x82, 0xdd, 0x8c, 0x4f, 0xf9, 0x6c, 0xbf, 0xfc, 0x8d, 0x02, 0xf3, 0xa2, 0x60, - 0xdf, 0xd6, 0xdc, 0xd8, 0x48, 0x9f, 0x87, 0x50, 0xdc, 0x79, 0xd6, 0x12, 0xd8, 0x38, 0x25, 0x89, - 0xba, 0x0f, 0xe7, 0x0e, 0xd9, 0xca, 0x72, 0x93, 0xb6, 0xe1, 0x11, 0x9d, 0xbd, 0x33, 0x3c, 0xa6, - 0x88, 0xe7, 0x31, 0xca, 0x4d, 0xd6, 0x93, 0x8b, 0x38, 0x4d, 0x3b, 0xfd, 0xdd, 0x55, 0xaf, 0xc2, - 0xe2, 0x41, 0xcf, 0x3a, 0xc2, 0xae, 0x0f, 0x67, 0xa1, 0xb8, 0xea, 0xba, 0x2d, 0x97, 0xe8, 0xe8, - 0x16, 0x20, 0x9f, 0x78, 0x7d, 0x43, 0x27, 0xab, 0xba, 0xee, 0x04, 0x36, 0xbd, 0x1b, 0xef, 0x5d, - 0x92, 0x7b, 0x51, 0x6b, 0x8c, 0x02, 0x4f, 0xd8, 0x85, 0xde, 0x81, 0xa2, 0x2e, 0xba, 0x2b, 0xf2, - 0x72, 0xee, 0x67, 0xd2, 0x09, 0x12, 0x25, 0xbe, 0x1c, 0xe0, 0x10, 0x12, 0xed, 0xc3, 0xcc, 0x1e, - 0x53, 0x04, 0x6f, 0x4b, 0xcc, 0xad, 0xb4, 0x32, 0x30, 0x8c, 0x38, 0x24, 0xf0, 0x21, 0x16, 0x80, - 0xe8, 0x27, 0x0a, 0x94, 0x28, 0xb1, 0x5c, 0x53, 0xa3, 0xa4, 0x52, 0xe0, 0xe8, 0xc7, 0x5c, 0xd0, - 0xed, 0x48, 0xee, 0x8d, 0x45, 0x29, 0x40, 0x29, 0x9c, 0xc1, 0x11, 0x32, 0x7a, 0x07, 0x66, 0xdb, - 0xbc, 0x29, 0x50, 0x99, 0xe1, 0x32, 0xec, 0x64, 0xd1, 0xe2, 0x88, 0x13, 0x5e, 0x31, 0xc6, 0x12, - 0x93, 0x25, 0xc6, 0xae, 0x16, 0xf8, 0xa4, 0xcd, 0x2b, 0x98, 0x52, 0x4c, 0xd7, 0xe4, 0xb3, 0x58, - 0xae, 0xa2, 0xaf, 0x41, 0x49, 0xd7, 0x6c, 0x9d, 0x98, 0xa4, 0xcd, 0x8b, 0xac, 0x52, 0x7c, 0xa6, - 0x35, 0x39, 0x8f, 0x23, 0x0a, 0xf4, 0x26, 0x80, 0x3f, 0xb0, 0xf5, 0x26, 0xf1, 0x0c, 0xa7, 0x5d, - 0x29, 0x71, 0xab, 0xaa, 0xd5, 0x44, 0xdb, 0xb3, 0x96, 0x6c, 0x7b, 0xc6, 0xc2, 0x5b, 0x84, 0x6a, - 0xb5, 0xfe, 0x95, 0xda, 0x7a, 0xe0, 0x69, 0xfc, 0xed, 0xe3, 0xa9, 0x5f, 0x2b, 0xe2, 0x82, 0x13, - 0x1c, 0xd1, 0x0a, 0x80, 0xed, 0x38, 0xb2, 0x13, 0x53, 0x29, 0x73, 0x79, 0x90, 0x94, 0x07, 0xee, - 0x46, 0x2b, 0x38, 0x41, 0xa5, 0xfe, 0xbe, 0xc8, 0x0b, 0xc0, 0xf0, 0x06, 0x10, 0x85, 0xfc, 0x80, - 0xd2, 0x6c, 0xfa, 0x26, 0x21, 0xc8, 0x43, 0x2a, 0x8b, 0xf9, 0x87, 0x94, 0x62, 0x06, 0x87, 0xf6, - 0xa1, 0xd0, 0xdb, 0x35, 0xdb, 0xd2, 0xd3, 0x5e, 0xcf, 0x06, 0xf6, 0xf6, 0xae, 0xd9, 0x96, 0xfd, - 0xc5, 0x5d, 0xb3, 0x8d, 0x39, 0x22, 0x8f, 0xc4, 0xac, 0x90, 0x0f, 0x89, 0x64, 0xe2, 0xb2, 0x9b, - 0x8d, 0x08, 0x37, 0x13, 0x48, 0x22, 0x12, 0x27, 0x67, 0x70, 0x4a, 0x12, 0xf4, 0x0b, 0x05, 0xca, - 0xbd, 0xc0, 0xa7, 0x8e, 0x65, 0xbc, 0x4d, 0xb2, 0xe9, 0x38, 0x46, 0xaa, 0x09, 0x61, 0xc4, 0x9b, - 0x15, 0x0d, 0x71, 0x2c, 0x00, 0x0b, 0x0c, 0xc5, 0xef, 0xfa, 0x8e, 0x6d, 0x93, 0xb0, 0xdd, 0xf2, - 0x46, 0x36, 0xc2, 0xdc, 0x12, 0x20, 0x22, 0x32, 0xca, 0x01, 0x0e, 0xa1, 0x99, 0xa9, 0xf8, 0x8e, - 0xeb, 0xcb, 0xd6, 0x42, 0x46, 0xa6, 0xd2, 0x72, 0x5c, 0x5f, 0x98, 0x0a, 0xfb, 0xc2, 0x1c, 0x91, - 0xb9, 0x86, 0x1e, 0x10, 0xee, 0xe7, 0x99, 0xb9, 0xc6, 0x5a, 0x40, 0x84, 0x6b, 0xac, 0x05, 0x04, - 0x33, 0x38, 0x96, 0x97, 0x9f, 0x4c, 0x13, 0xa0, 0x6a, 0x32, 0x81, 0x94, 0x9d, 0xed, 0x54, 0x5a, - 0xf7, 0x81, 0x02, 0xc0, 0xf3, 0xa2, 0x64, 0x5e, 0x97, 0x91, 0x49, 0x3f, 0xe0, 0x38, 0x32, 0xb5, - 0x8b, 0xa2, 0xcd, 0x83, 0x08, 0x1d, 0x27, 0x24, 0x41, 0xab, 0x70, 0xca, 0xb0, 0xdd, 0x80, 0xde, - 0xd8, 0x77, 0x3d, 0xe2, 0xfb, 0x61, 0xa1, 0x50, 0x6e, 0x9c, 0x93, 0x1b, 0x4f, 0x6d, 0xa5, 0x97, - 0xf1, 0x41, 0x7a, 0xb4, 0x0e, 0x8b, 0x4e, 0x40, 0xd3, 0x3c, 0x44, 0x27, 0xa0, 0x22, 0x79, 0x2c, - 0xde, 0x3b, 0xb0, 0x8e, 0xc7, 0x76, 0xa8, 0x7f, 0xcc, 0xf1, 0x34, 0x67, 0x92, 0x5f, 0x1e, 0xa1, - 0x43, 0x90, 0xfa, 0x8d, 0x29, 0x77, 0x84, 0xdf, 0x98, 0xc2, 0x46, 0x5c, 0xfe, 0xb0, 0x46, 0xdc, - 0xc1, 0x2b, 0x2b, 0xbc, 0x28, 0x57, 0xa6, 0x9e, 0x01, 0x34, 0xee, 0x9b, 0xea, 0x0a, 0x9c, 0x3a, - 0x10, 0x59, 0xa7, 0x5a, 0xa5, 0xfa, 0x25, 0x9e, 0x9e, 0x8f, 0x85, 0x1c, 0xf5, 0x0f, 0xb9, 0x14, - 0x33, 0xe6, 0x71, 0x68, 0x1f, 0xf2, 0x6e, 0x27, 0xfc, 0xb5, 0xe9, 0x8d, 0xec, 0x9c, 0xbc, 0xb9, - 0xd9, 0x14, 0xfe, 0xd6, 0xdc, 0x6c, 0x62, 0x06, 0x19, 0x1f, 0x23, 0x77, 0x88, 0x73, 0xed, 0x43, - 0x9e, 0x55, 0xc3, 0xf9, 0xac, 0x45, 0x5b, 0xed, 0xc8, 0x50, 0xb0, 0xda, 0x21, 0x98, 0x41, 0xaa, - 0x1f, 0x2b, 0xa9, 0xbb, 0x90, 0x44, 0xe8, 0x43, 0x05, 0xce, 0xb8, 0x9e, 0xd1, 0x67, 0x4a, 0x25, - 0x03, 0xbf, 0x75, 0xa0, 0xd2, 0xb1, 0x33, 0xd4, 0xde, 0x04, 0xd4, 0x46, 0x65, 0x34, 0xac, 0x9e, - 0x99, 0xb4, 0x82, 0x27, 0x4a, 0x39, 0xe9, 0x54, 0xcd, 0xcd, 0xe6, 0x17, 0xfd, 0x54, 0x9b, 0xf0, - 0xf2, 0x11, 0xd8, 0x1e, 0xa1, 0xbe, 0xf9, 0x93, 0x02, 0x5f, 0x19, 0xf3, 0xdd, 0x75, 0xe7, 0xb1, - 0xfd, 0x58, 0xf3, 0xda, 0xab, 0xcd, 0x2d, 0x56, 0xc3, 0xcf, 0x18, 0x94, 0x58, 0x61, 0x3f, 0xa1, - 0x97, 0x65, 0xd0, 0x48, 0x00, 0x6f, 0x51, 0x62, 0x25, 0xba, 0x41, 0x4c, 0x02, 0x2c, 0x04, 0x51, - 0x03, 0xb8, 0x30, 0x6d, 0xe7, 0xd1, 0xa2, 0xec, 0x9e, 0x41, 0xcc, 0x76, 0xa2, 0xd4, 0x8c, 0xa2, - 0xec, 0x46, 0xb8, 0x80, 0x63, 0x1a, 0xf5, 0xbd, 0x42, 0x2a, 0xa8, 0x27, 0xc3, 0x1c, 0xfa, 0xd5, - 0x84, 0x1e, 0x00, 0xc9, 0x3e, 0xbc, 0x3e, 0xbb, 0x0d, 0xf0, 0xbb, 0xc9, 0x6d, 0x80, 0xe7, 0x24, - 0xd7, 0x94, 0x4e, 0xc0, 0x11, 0x1e, 0xad, 0xdf, 0x2a, 0x30, 0xd7, 0x8e, 0xef, 0x55, 0xe6, 0xa8, - 0x9d, 0xe7, 0x64, 0x80, 0x8d, 0x53, 0xa3, 0x61, 0x75, 0x2e, 0x31, 0x81, 0x93, 0xc2, 0xa8, 0xdf, - 0x84, 0xa5, 0xc3, 0x0f, 0x7f, 0x04, 0xc7, 0xfb, 0x34, 0x9f, 0x4a, 0xbc, 0x1e, 0x52, 0x8a, 0x5a, - 0x70, 0xd6, 0xe8, 0xd8, 0x8e, 0x47, 0xee, 0xdb, 0x3d, 0xdb, 0x79, 0x6c, 0xaf, 0x39, 0x96, 0x45, - 0x6c, 0x2a, 0xfe, 0x89, 0x50, 0x6a, 0x9c, 0x97, 0x5c, 0xce, 0x6e, 0x4d, 0x22, 0xc2, 0x93, 0xf7, - 0xf2, 0x1f, 0x61, 0xa8, 0x67, 0xe8, 0x94, 0xdf, 0x7e, 0xa2, 0xd6, 0x6c, 0xf1, 0x59, 0x2c, 0x57, - 0x13, 0x3f, 0x6a, 0xe7, 0x3f, 0x8f, 0x1f, 0xb5, 0xa3, 0x77, 0xb1, 0x70, 0xc8, 0xbb, 0xf8, 0x55, - 0xe6, 0xae, 0x26, 0xd9, 0xd6, 0xbc, 0x9e, 0xcf, 0x8b, 0xf6, 0xb2, 0xb0, 0xfc, 0x8d, 0x70, 0x12, - 0xc7, 0xeb, 0x07, 0xd3, 0x9d, 0xd9, 0x17, 0x26, 0xdd, 0xf9, 0x87, 0x02, 0xe5, 0x35, 0xc7, 0x6e, - 0x1b, 0xbc, 0x93, 0x7c, 0x05, 0x0a, 0x74, 0xe0, 0x86, 0x56, 0x12, 0xde, 0x6f, 0x61, 0x67, 0xe0, - 0x92, 0xcf, 0x86, 0xd5, 0x85, 0x88, 0x90, 0x4d, 0x60, 0x4e, 0x8a, 0xee, 0xb0, 0xeb, 0xd4, 0x68, - 0xe0, 0xcb, 0x90, 0x75, 0x35, 0xbe, 0x4e, 0x36, 0xfb, 0xd9, 0xb0, 0xaa, 0xc6, 0x7f, 0x74, 0xaa, - 0xeb, 0x8e, 0x47, 0xea, 0xfd, 0x2b, 0xb5, 0x88, 0x93, 0xa0, 0xc2, 0x92, 0x07, 0x33, 0x0e, 0x8f, - 0x68, 0x7e, 0x94, 0x27, 0x47, 0xc6, 0x81, 0xf9, 0x2c, 0x96, 0xab, 0xe8, 0x12, 0x14, 0x2d, 0xe2, - 0xfb, 0x2c, 0x31, 0x39, 0xf0, 0xb3, 0xd8, 0xb6, 0x98, 0xc6, 0xe1, 0xba, 0xfa, 0xc3, 0x3c, 0x2c, - 0x6c, 0x12, 0x9b, 0x78, 0x86, 0x2e, 0xc0, 0xd0, 0x2d, 0x40, 0xce, 0xae, 0x4f, 0xbc, 0x3e, 0x69, - 0x6f, 0x8a, 0x3f, 0x57, 0xb1, 0xa4, 0x9a, 0x9d, 0x39, 0x1f, 0xb7, 0xcd, 0xee, 0x8d, 0x51, 0xe0, - 0x09, 0xbb, 0xd0, 0xbb, 0x0a, 0x80, 0x1e, 0x1e, 0xc6, 0x97, 0xa5, 0xc7, 0x71, 0x36, 0x6f, 0x22, - 0x4d, 0xc5, 0x57, 0x19, 0x4d, 0xf9, 0x38, 0x81, 0x8d, 0xb6, 0xe1, 0xf4, 0x9e, 0x67, 0x10, 0xbb, - 0x6d, 0x0e, 0xd6, 0x89, 0xaf, 0x7b, 0x86, 0x4b, 0xe3, 0x82, 0xe3, 0xcb, 0x72, 0xf3, 0xe9, 0x8d, - 0x71, 0x12, 0x3c, 0x69, 0x1f, 0xd3, 0x52, 0xe0, 0x93, 0xbd, 0xc0, 0xbc, 0xe1, 0x79, 0x8e, 0xb7, - 0x9d, 0xd2, 0x76, 0xa4, 0xa5, 0xfb, 0x63, 0x14, 0x78, 0xc2, 0xae, 0x46, 0xed, 0xc9, 0xd3, 0xe5, - 0x13, 0x1f, 0x3d, 0x5d, 0x3e, 0xf1, 0xc9, 0xd3, 0xe5, 0x13, 0x3f, 0x18, 0x2d, 0x2b, 0x4f, 0x46, - 0xcb, 0xca, 0x47, 0xa3, 0x65, 0xe5, 0x93, 0xd1, 0xb2, 0xf2, 0xcf, 0xd1, 0xb2, 0xf2, 0xfe, 0xbf, - 0x96, 0x4f, 0xbc, 0x5e, 0x0a, 0x0f, 0xfd, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x63, 0x86, 0x5b, - 0x2b, 0xac, 0x28, 0x00, 0x00, + 0x15, 0xf6, 0x8a, 0x94, 0x28, 0x3e, 0xfd, 0x58, 0x1e, 0x5b, 0x0d, 0xab, 0x36, 0xa2, 0xb1, 0x01, + 0x82, 0x18, 0xad, 0x49, 0x58, 0x70, 0x5c, 0xa3, 0x05, 0x0a, 0x88, 0x92, 0x25, 0xcb, 0xb2, 0x6c, + 0x62, 0x28, 0xbb, 0x71, 0xda, 0xc4, 0x59, 0x2e, 0x47, 0xe4, 0x96, 0xcb, 0xdd, 0xed, 0xee, 0x2c, + 0x2d, 0x06, 0x29, 0xd0, 0x3f, 0x14, 0x69, 0x53, 0xa0, 0x46, 0x81, 0xa4, 0x45, 0x0b, 0xf4, 0x52, + 0xa0, 0xf7, 0xa2, 0x3d, 0xe6, 0x52, 0xf4, 0x62, 0xa0, 0x28, 0x10, 0x20, 0x97, 0xf4, 0xc2, 0xd6, + 0xec, 0xa9, 0xc7, 0x22, 0xb7, 0x9c, 0x8a, 0xf9, 0xd9, 0x3f, 0x92, 0x32, 0x55, 0x40, 0xeb, 0x38, + 0x40, 0x6f, 0xbb, 0x6f, 0xde, 0xbc, 0xef, 0xcd, 0x9b, 0x99, 0xf7, 0xb7, 0x0b, 0xf7, 0x9a, 0x06, + 0x6d, 0xf9, 0xf5, 0x92, 0x6e, 0x77, 0xca, 0xdd, 0xce, 0x03, 0xcd, 0x25, 0x17, 0xa9, 0x66, 0xbd, + 0xe9, 0x97, 0x75, 0xcd, 0xed, 0x12, 0xf3, 0x62, 0x5b, 0x73, 0x9c, 0x8b, 0xba, 0x6d, 0x51, 0xd7, + 0x36, 0x4d, 0xe2, 0x96, 0x9d, 0x76, 0xb3, 0xac, 0x39, 0x86, 0x57, 0x66, 0x03, 0x3a, 0x75, 0xcd, + 0x72, 0xf7, 0x92, 0x66, 0x3a, 0x2d, 0xed, 0x52, 0xb9, 0x49, 0x2c, 0xe2, 0x6a, 0x94, 0x34, 0x4a, + 0x8e, 0x6b, 0x53, 0x1b, 0xed, 0x44, 0xa2, 0x4b, 0x42, 0xf4, 0x7d, 0x2e, 0xba, 0x24, 0x44, 0xdf, + 0x67, 0x12, 0xee, 0x47, 0xa2, 0x4b, 0x4e, 0xbb, 0x59, 0x62, 0xa2, 0x4b, 0x81, 0xe8, 0x52, 0x20, + 0x7a, 0xe5, 0x62, 0x4c, 0xcb, 0xa6, 0xdd, 0xb4, 0xcb, 0x1c, 0xa1, 0xee, 0x1f, 0xf0, 0x37, 0xfe, + 0xc2, 0x9f, 0x04, 0xf2, 0x0a, 0x9e, 0xb0, 0xa8, 0x2e, 0xb1, 0x1a, 0x86, 0x58, 0x8b, 0x7c, 0xec, + 0x12, 0xd7, 0x33, 0x6c, 0xcb, 0x3b, 0x72, 0x35, 0x2b, 0x97, 0xdb, 0x57, 0xbd, 0x92, 0x61, 0xb3, + 0xc5, 0x77, 0x34, 0xbd, 0x65, 0x58, 0xc4, 0xed, 0x45, 0xd6, 0xe8, 0x10, 0xaa, 0x95, 0xbb, 0xa3, + 0xb3, 0xca, 0x47, 0xcd, 0x72, 0x7d, 0x8b, 0x1a, 0x1d, 0x32, 0x32, 0xe1, 0xca, 0xa4, 0x09, 0x9e, + 0xde, 0x22, 0x1d, 0x6d, 0x78, 0x9e, 0xfa, 0xb1, 0x02, 0xb0, 0xee, 0x38, 0x1b, 0xa6, 0xef, 0x51, + 0xe2, 0xa2, 0x32, 0xe4, 0x2d, 0xad, 0x43, 0x3c, 0x47, 0xd3, 0x49, 0x41, 0x39, 0xaf, 0xbc, 0x94, + 0xaf, 0x9c, 0x79, 0xd4, 0x2f, 0x9e, 0x1a, 0xf4, 0x8b, 0xf9, 0x5b, 0xc1, 0x00, 0x8e, 0x78, 0xd0, + 0x1f, 0x15, 0x38, 0xdb, 0xf6, 0xeb, 0x44, 0xb7, 0xad, 0x03, 0xa3, 0x59, 0x23, 0xba, 0x4b, 0x28, + 0x26, 0x07, 0x85, 0xa9, 0xf3, 0xca, 0x4b, 0x73, 0x6b, 0xad, 0xd2, 0x89, 0xed, 0x65, 0x29, 0xd2, + 0x72, 0x77, 0x14, 0xaf, 0xf2, 0xdc, 0xa0, 0x5f, 0x3c, 0x3b, 0x66, 0x00, 0x8f, 0xd3, 0x4e, 0x7d, + 0x03, 0x9e, 0x7f, 0xa2, 0x38, 0x74, 0x1e, 0xb2, 0x6c, 0x8d, 0xd2, 0x04, 0xf3, 0xd2, 0x04, 0x59, + 0x66, 0x02, 0xcc, 0x47, 0xd0, 0xf3, 0x90, 0x69, 0x93, 0x1e, 0x5f, 0x67, 0xbe, 0x32, 0x27, 0x19, + 0x32, 0xbb, 0xa4, 0x87, 0x19, 0x5d, 0xfd, 0xa1, 0x02, 0xf9, 0x75, 0xc7, 0xd9, 0x24, 0x8e, 0x69, + 0xf7, 0x50, 0x17, 0xb2, 0x6c, 0x41, 0x5c, 0xdc, 0xdc, 0xda, 0x2b, 0x27, 0x6b, 0x15, 0x81, 0xb1, + 0xab, 0x39, 0x4e, 0x65, 0x96, 0x29, 0xc9, 0x9e, 0x30, 0xc7, 0x53, 0xdf, 0xcb, 0xc0, 0x42, 0x82, + 0x03, 0xbd, 0x08, 0x33, 0x86, 0x45, 0xed, 0x5b, 0x9e, 0x5c, 0xda, 0xa2, 0xd4, 0x7c, 0x66, 0x87, + 0x53, 0xb1, 0x1c, 0x45, 0x45, 0x98, 0xee, 0x68, 0xce, 0x2d, 0xaf, 0x30, 0x75, 0x3e, 0xf3, 0x52, + 0xbe, 0x92, 0x1f, 0xf4, 0x8b, 0xd3, 0x7b, 0x8c, 0x80, 0x05, 0x1d, 0x95, 0x00, 0x5c, 0xed, 0xc1, + 0x6d, 0x87, 0xb2, 0xe3, 0x5f, 0xc8, 0x70, 0xae, 0xc5, 0x41, 0xbf, 0x08, 0x38, 0xa4, 0xe2, 0x18, + 0x07, 0xfa, 0xb1, 0x02, 0x39, 0xc3, 0xf2, 0x1c, 0xa2, 0xd3, 0x42, 0x96, 0x9b, 0xe1, 0x7e, 0x5a, + 0x66, 0xd8, 0x11, 0x30, 0x95, 0xb9, 0x41, 0xbf, 0x98, 0x93, 0x2f, 0x38, 0x00, 0x47, 0x3f, 0x50, + 0x60, 0xa6, 0x41, 0x4c, 0x42, 0x49, 0x61, 0x9a, 0xeb, 0xf1, 0x7a, 0x5a, 0x7a, 0x6c, 0x72, 0x94, + 0x0a, 0x30, 0xf3, 0x8a, 0x67, 0x2c, 0x91, 0xd5, 0x6b, 0x70, 0x76, 0x0c, 0xeb, 0x90, 0x51, 0x95, + 0x49, 0x46, 0x55, 0xb7, 0xe0, 0xdc, 0xb8, 0x95, 0xff, 0xcf, 0x72, 0xfe, 0x34, 0x03, 0xb3, 0xeb, + 0x8e, 0xb3, 0x45, 0xa8, 0xde, 0x42, 0xdf, 0x65, 0x47, 0xc4, 0x34, 0x2c, 0x22, 0x8f, 0xeb, 0xbd, + 0x93, 0xb5, 0x0f, 0x07, 0xd9, 0xe1, 0x00, 0xc2, 0x34, 0xe2, 0x19, 0x4b, 0x50, 0xd4, 0x83, 0x69, + 0xa3, 0xa3, 0x35, 0x89, 0x74, 0x21, 0xaf, 0xa4, 0x81, 0xce, 0xe4, 0x8b, 0x33, 0xcd, 0x1f, 0xb1, + 0x40, 0x44, 0x3e, 0x64, 0x5b, 0x94, 0x3a, 0x85, 0x0c, 0x47, 0xfe, 0x46, 0x0a, 0xc8, 0xd7, 0xf7, + 0xf7, 0xab, 0xe2, 0x96, 0xb2, 0x27, 0xcc, 0xe1, 0xd0, 0x77, 0x20, 0xd3, 0x34, 0x82, 0x5b, 0x71, + 0x37, 0x05, 0xd4, 0x6d, 0x83, 0x56, 0x72, 0xcc, 0x3d, 0x6d, 0x1b, 0x14, 0x33, 0x2c, 0xf4, 0x13, + 0x05, 0xf2, 0x2d, 0x62, 0x76, 0x36, 0x5a, 0x9a, 0x4b, 0xe5, 0x3d, 0xf8, 0x56, 0x1a, 0xeb, 0x0d, + 0x30, 0x2a, 0x0b, 0x2c, 0x84, 0x84, 0xaf, 0x38, 0x42, 0x47, 0xef, 0x2a, 0x30, 0x6f, 0x74, 0x9a, + 0x4e, 0xbb, 0x59, 0xf1, 0xad, 0x86, 0x49, 0x0a, 0x33, 0x69, 0xb8, 0x07, 0xb9, 0xf1, 0x11, 0x4c, + 0x65, 0x69, 0xd0, 0x2f, 0xce, 0xc7, 0x29, 0x38, 0xa1, 0x06, 0x8b, 0x01, 0x8e, 0x46, 0x5b, 0x85, + 0x5c, 0x32, 0x06, 0x54, 0x35, 0xda, 0xc2, 0x7c, 0x44, 0xfd, 0x6b, 0x06, 0xe6, 0x62, 0x36, 0x66, + 0x31, 0xc1, 0x77, 0x4d, 0xe9, 0x59, 0xc3, 0x98, 0x70, 0x07, 0xdf, 0xc4, 0x8c, 0xce, 0x86, 0x5d, + 0x19, 0x1a, 0x63, 0xc3, 0x2c, 0x48, 0x31, 0x3a, 0xfa, 0xa9, 0x02, 0xf3, 0x2e, 0x39, 0xa8, 0x11, + 0x93, 0xe8, 0xec, 0x5a, 0x4a, 0x3b, 0xec, 0x4f, 0xb2, 0x83, 0x48, 0x45, 0xf8, 0xf2, 0xe5, 0x63, + 0x90, 0x95, 0x44, 0x06, 0xb8, 0x2b, 0x28, 0xa1, 0x6c, 0xb1, 0x78, 0x1c, 0x43, 0xc3, 0x09, 0x6c, + 0xf4, 0xb6, 0x02, 0x79, 0x2f, 0x8c, 0xe6, 0xe2, 0x42, 0x7c, 0x33, 0x85, 0x1d, 0xb9, 0x69, 0xeb, + 0x9a, 0xc9, 0x02, 0x38, 0x3f, 0x1f, 0x51, 0xd8, 0x8e, 0xc0, 0xd1, 0x05, 0xc8, 0x79, 0x7e, 0x9d, + 0x99, 0x9d, 0x5f, 0x91, 0x7c, 0xe5, 0xb4, 0x34, 0x5d, 0xae, 0x26, 0xc8, 0x38, 0x18, 0x47, 0x5f, + 0x83, 0x05, 0xf3, 0xc0, 0xab, 0xb5, 0x0d, 0xa7, 0xd6, 0xf1, 0x1b, 0x4d, 0xe1, 0xe1, 0x67, 0x2b, + 0xcb, 0x72, 0xc2, 0xc2, 0xcd, 0xad, 0x5a, 0x34, 0x88, 0x93, 0xbc, 0xea, 0x3b, 0x53, 0x30, 0x1f, + 0xbf, 0xa7, 0x93, 0xb6, 0xf3, 0x45, 0x98, 0xf1, 0x5a, 0xda, 0xda, 0xcb, 0x57, 0xe4, 0x8e, 0x86, + 0xa1, 0xb4, 0x76, 0x7d, 0x7d, 0xed, 0xe5, 0x2b, 0x58, 0x8e, 0x7e, 0x36, 0x4d, 0xc9, 0x12, 0xc3, + 0x33, 0x23, 0xb7, 0xf8, 0x18, 0x79, 0xd1, 0x05, 0xc8, 0xc9, 0xe3, 0x27, 0xcd, 0x12, 0x42, 0xc8, + 0x33, 0x88, 0x83, 0x71, 0xf4, 0x50, 0x01, 0x70, 0x89, 0x63, 0x7b, 0x06, 0xb5, 0xdd, 0x9e, 0xb4, + 0xcc, 0x1b, 0x69, 0x7a, 0x21, 0x4c, 0x1c, 0x5b, 0x06, 0xc2, 0x10, 0x17, 0xc7, 0x74, 0x50, 0xff, + 0xac, 0xc0, 0xf2, 0xd8, 0x59, 0x93, 0x0e, 0x43, 0x72, 0x93, 0xa7, 0x3e, 0xc5, 0x4d, 0x56, 0xff, + 0x33, 0xc5, 0x93, 0xbe, 0x28, 0xd2, 0x4d, 0xd2, 0x9d, 0x39, 0x1e, 0xaa, 0x35, 0x23, 0xc7, 0x93, + 0x4d, 0xdb, 0xf1, 0xec, 0xc7, 0xd0, 0x70, 0x02, 0xfb, 0x19, 0x32, 0x64, 0xfc, 0xb6, 0x64, 0x26, + 0xdc, 0x96, 0x0f, 0xa7, 0x78, 0x26, 0x36, 0x12, 0x64, 0xd0, 0x0b, 0x41, 0x36, 0x23, 0x8c, 0xbf, + 0x20, 0x25, 0x24, 0xf3, 0x8e, 0x91, 0x0d, 0xc8, 0xfc, 0x7f, 0x03, 0xd8, 0x49, 0xfe, 0xf7, 0x14, + 0x2c, 0x26, 0x33, 0x46, 0x96, 0xb8, 0x4c, 0xb3, 0xd8, 0x2b, 0xb2, 0xda, 0xb9, 0xb5, 0x46, 0x6a, + 0xc9, 0x69, 0x89, 0x6d, 0xac, 0x77, 0xcd, 0xa2, 0x6e, 0x2f, 0xda, 0x36, 0x4e, 0xc3, 0x42, 0x03, + 0xe6, 0xbf, 0xf2, 0xfc, 0x69, 0xcb, 0xb5, 0x3b, 0xbc, 0x50, 0x4a, 0x29, 0x6b, 0xe1, 0xfa, 0xd4, + 0x6c, 0xdf, 0xd5, 0x49, 0x54, 0x8e, 0x57, 0x03, 0x64, 0x1c, 0x29, 0xb1, 0x72, 0x15, 0x20, 0x52, + 0x1b, 0x2d, 0x89, 0x1a, 0x95, 0x1f, 0x3d, 0x5e, 0x96, 0xa2, 0x73, 0x30, 0xdd, 0xd5, 0x4c, 0x5f, + 0x24, 0xd7, 0x79, 0x2c, 0x5e, 0xbe, 0x3a, 0x75, 0x55, 0x51, 0xff, 0x12, 0x3f, 0xc1, 0x31, 0x40, + 0xf4, 0xf3, 0xc4, 0x79, 0x10, 0x25, 0x41, 0x3d, 0xe5, 0x55, 0x3e, 0xf9, 0x5e, 0xfe, 0x4a, 0x81, + 0x79, 0x51, 0xb0, 0xef, 0x69, 0x4e, 0x74, 0x48, 0x9f, 0x86, 0x52, 0xfc, 0xf2, 0x6c, 0xc4, 0xb0, + 0x71, 0x42, 0x13, 0xf5, 0x10, 0x9e, 0x3b, 0x62, 0x2a, 0xcb, 0x4d, 0x1a, 0x86, 0x4b, 0x74, 0x16, + 0x67, 0xb8, 0x4f, 0x11, 0xe1, 0x31, 0xcc, 0x4d, 0x36, 0xe3, 0x83, 0x38, 0xc9, 0x3b, 0x39, 0xee, + 0xaa, 0x97, 0x61, 0x69, 0xf8, 0x66, 0x1d, 0x63, 0xd6, 0xfb, 0x33, 0x90, 0x5b, 0x77, 0x9c, 0x9a, + 0x43, 0x74, 0x74, 0x03, 0x90, 0x47, 0xdc, 0xae, 0xa1, 0x93, 0x75, 0x5d, 0xb7, 0x7d, 0x8b, 0xde, + 0x8a, 0xe6, 0xae, 0xc8, 0xb9, 0xa8, 0x36, 0xc2, 0x81, 0xc7, 0xcc, 0x42, 0x6f, 0x41, 0x4e, 0x17, + 0xdd, 0x15, 0xb9, 0x39, 0x77, 0x52, 0xe9, 0x04, 0x89, 0x12, 0x5f, 0xbe, 0xe0, 0x00, 0x12, 0x1d, + 0xc2, 0xf4, 0x01, 0x33, 0x04, 0x6f, 0x4b, 0xcc, 0xad, 0xd5, 0x52, 0x38, 0x18, 0x91, 0x4b, 0xe0, + 0xaf, 0x58, 0x00, 0xa2, 0x1f, 0x29, 0x30, 0x4b, 0x49, 0xc7, 0x31, 0x35, 0x4a, 0x0a, 0x59, 0x8e, + 0x7e, 0xc2, 0x05, 0xdd, 0xbe, 0x94, 0x5e, 0x59, 0x92, 0x0a, 0xcc, 0x06, 0x14, 0x1c, 0x22, 0xa3, + 0xb7, 0x60, 0xa6, 0xc1, 0x9b, 0x02, 0x85, 0x69, 0xae, 0xc3, 0x7e, 0x1a, 0x2d, 0x8e, 0x28, 0xe1, + 0x15, 0xef, 0x58, 0x62, 0xb2, 0xc4, 0xd8, 0xd1, 0x7c, 0x8f, 0x34, 0x78, 0x05, 0x33, 0x1b, 0xf1, + 0x55, 0x39, 0x15, 0xcb, 0x51, 0xf4, 0x65, 0x98, 0xd5, 0x35, 0x4b, 0x27, 0x26, 0x69, 0xf0, 0x22, + 0x6b, 0x36, 0x5a, 0xd3, 0x86, 0xa4, 0xe3, 0x90, 0x03, 0xbd, 0x0e, 0xe0, 0xf5, 0x2c, 0xbd, 0x4a, + 0x5c, 0xc3, 0x6e, 0x14, 0x66, 0xf9, 0xa9, 0x2a, 0x95, 0x44, 0xdb, 0xb3, 0x14, 0x6f, 0x7b, 0x46, + 0xca, 0x77, 0x08, 0xd5, 0x4a, 0xdd, 0x4b, 0xa5, 0x4d, 0xdf, 0xd5, 0x78, 0xec, 0xe3, 0xa9, 0x5f, + 0x2d, 0x94, 0x82, 0x63, 0x12, 0xd1, 0x1a, 0x80, 0x65, 0xdb, 0xb2, 0x13, 0x53, 0xc8, 0x73, 0x7d, + 0x90, 0xd4, 0x07, 0x6e, 0x85, 0x23, 0x38, 0xc6, 0xa5, 0xfe, 0x36, 0xc7, 0x0b, 0xc0, 0x60, 0x07, + 0x10, 0x85, 0x4c, 0x8f, 0xd2, 0x74, 0xfa, 0x26, 0x01, 0xc8, 0x3d, 0x2a, 0x8b, 0xf9, 0x7b, 0x94, + 0x62, 0x06, 0x87, 0x0e, 0x21, 0xdb, 0xae, 0x9b, 0x0d, 0x79, 0xd3, 0x5e, 0x4d, 0x07, 0x76, 0xb7, + 0x6e, 0x36, 0x64, 0x7f, 0xb1, 0x6e, 0x36, 0x30, 0x47, 0xe4, 0x9e, 0x98, 0x15, 0xf2, 0x01, 0x93, + 0x4c, 0x5c, 0xea, 0xe9, 0xa8, 0x70, 0x3d, 0x86, 0x24, 0x3c, 0x71, 0x9c, 0x82, 0x13, 0x9a, 0xa0, + 0x9f, 0x29, 0x90, 0x6f, 0xfb, 0x1e, 0xb5, 0x3b, 0xc6, 0x9b, 0x24, 0x9d, 0x8e, 0x63, 0x68, 0x9a, + 0x00, 0x46, 0xc4, 0xac, 0xf0, 0x15, 0x47, 0x0a, 0x30, 0xc7, 0x90, 0xfb, 0xb6, 0x67, 0x5b, 0x16, + 0x09, 0xda, 0x2d, 0xaf, 0xa5, 0xa3, 0xcc, 0x0d, 0x01, 0x22, 0x3c, 0xa3, 0x7c, 0xc1, 0x01, 0x34, + 0x3b, 0x2a, 0x9e, 0xed, 0x78, 0xb2, 0xb5, 0x90, 0xd2, 0x51, 0xa9, 0xd9, 0x8e, 0x27, 0x8e, 0x0a, + 0x7b, 0xc2, 0x1c, 0x91, 0x5d, 0x0d, 0xdd, 0x27, 0xfc, 0x9e, 0xa7, 0x76, 0x35, 0x36, 0x7c, 0x22, + 0xae, 0xc6, 0x86, 0x4f, 0x30, 0x83, 0x63, 0x79, 0xf9, 0x62, 0x92, 0x01, 0x15, 0xe3, 0x09, 0xa4, + 0xec, 0x6c, 0x27, 0xd2, 0xba, 0xf7, 0x14, 0x00, 0x9e, 0x17, 0xc5, 0xf3, 0xba, 0x94, 0x8e, 0xf4, + 0x5d, 0x8e, 0x23, 0x53, 0xbb, 0xd0, 0xdb, 0xdc, 0x0d, 0xd1, 0x71, 0x4c, 0x13, 0xb4, 0x0e, 0xa7, + 0x0d, 0xcb, 0xf1, 0xe9, 0xb5, 0x43, 0xc7, 0x25, 0x9e, 0x17, 0x14, 0x0a, 0xf9, 0xca, 0x73, 0x72, + 0xe2, 0xe9, 0x9d, 0xe4, 0x30, 0x1e, 0xe6, 0x47, 0x9b, 0xb0, 0x64, 0xfb, 0x34, 0x29, 0x43, 0x74, + 0x02, 0x0a, 0x52, 0xc6, 0xd2, 0xed, 0xa1, 0x71, 0x3c, 0x32, 0x43, 0xfd, 0x7b, 0x96, 0xa7, 0x39, + 0xe3, 0xee, 0xe5, 0x31, 0x3a, 0x04, 0x89, 0x6f, 0x4c, 0x53, 0xc7, 0xf8, 0xc6, 0x14, 0x34, 0xe2, + 0x32, 0x47, 0x35, 0xe2, 0x86, 0xb7, 0x2c, 0xfb, 0xcc, 0x6c, 0xd9, 0x2f, 0x15, 0x38, 0xd3, 0xf6, + 0xeb, 0xc4, 0xb5, 0x08, 0x25, 0x9e, 0xac, 0xc5, 0xa4, 0x03, 0xc0, 0x27, 0xa8, 0x9f, 0x94, 0x5c, + 0x59, 0x1e, 0xf4, 0x8b, 0x67, 0x76, 0x87, 0x01, 0xf1, 0xa8, 0x0e, 0xe8, 0x5d, 0x05, 0x16, 0x23, + 0xea, 0x7a, 0x75, 0x27, 0x70, 0x0a, 0x27, 0x79, 0x37, 0x77, 0x13, 0x00, 0x15, 0x34, 0xe8, 0x17, + 0x17, 0x93, 0x34, 0x3c, 0xa4, 0x84, 0x7a, 0x0e, 0xd0, 0xa8, 0x37, 0x53, 0xd7, 0xe0, 0xf4, 0x50, + 0x2c, 0x9a, 0x78, 0x8f, 0xd5, 0xcf, 0xf1, 0x82, 0x66, 0xc4, 0x49, 0xab, 0xbf, 0x9f, 0x4a, 0x08, + 0x63, 0x3e, 0x0a, 0x1d, 0x42, 0xc6, 0x69, 0x06, 0xdf, 0xe7, 0x5e, 0x4b, 0xcf, 0x2d, 0x56, 0xb7, + 0xab, 0xc2, 0x43, 0x55, 0xb7, 0xab, 0x98, 0x41, 0x46, 0xcb, 0x98, 0x3a, 0xc2, 0x1d, 0x1d, 0x42, + 0x46, 0x6b, 0x06, 0x91, 0x35, 0x45, 0xd5, 0xd6, 0x9b, 0xd2, 0x79, 0xae, 0x37, 0x09, 0x66, 0x90, + 0xea, 0x87, 0x4a, 0x62, 0x2f, 0x24, 0x13, 0x7a, 0x5f, 0x81, 0x73, 0x8e, 0x6b, 0x74, 0x99, 0x51, + 0x49, 0xcf, 0xab, 0x0d, 0xd5, 0x86, 0x56, 0x8a, 0xd6, 0x1b, 0x83, 0x5a, 0x29, 0x0c, 0xfa, 0xc5, + 0x73, 0xe3, 0x46, 0xf0, 0x58, 0x2d, 0xc7, 0xad, 0xaa, 0xba, 0x5d, 0xfd, 0xac, 0xaf, 0x6a, 0x1b, + 0x5e, 0x38, 0x86, 0xd8, 0x63, 0x54, 0x84, 0x7f, 0x50, 0xe0, 0x8b, 0x23, 0xde, 0x6e, 0xd3, 0x7e, + 0x60, 0x3d, 0xd0, 0xdc, 0xc6, 0x7a, 0x75, 0x07, 0x3d, 0x54, 0x60, 0xda, 0xa0, 0xa4, 0x13, 0x74, + 0x60, 0xda, 0x69, 0xba, 0xd9, 0x18, 0xf0, 0x0e, 0x25, 0x9d, 0x58, 0xff, 0x8c, 0x69, 0x80, 0x85, + 0x22, 0xea, 0xdf, 0xb2, 0x70, 0x7e, 0xd2, 0xd4, 0xe3, 0x05, 0xa6, 0x03, 0x83, 0x98, 0x8d, 0x58, + 0x75, 0x1e, 0x06, 0xa6, 0xad, 0x60, 0x00, 0x47, 0x3c, 0x47, 0x78, 0xf7, 0xcc, 0x33, 0xe0, 0xdd, + 0x7f, 0xa7, 0xc0, 0x32, 0x93, 0xb3, 0x11, 0xca, 0x0f, 0xb4, 0xcb, 0xa6, 0xa6, 0xdd, 0xe7, 0x07, + 0xfd, 0xe2, 0xf2, 0xee, 0x38, 0x50, 0x3c, 0x5e, 0x97, 0x71, 0x31, 0x68, 0xfa, 0x59, 0x88, 0x41, + 0xef, 0x24, 0xf3, 0x9b, 0x78, 0xc4, 0x47, 0xbf, 0x18, 0xd3, 0x0e, 0x23, 0xe9, 0x67, 0x1a, 0x4f, + 0xee, 0x88, 0xfd, 0x66, 0x7c, 0x47, 0xec, 0x29, 0xe9, 0x35, 0xa1, 0x29, 0x76, 0x8c, 0xfc, 0xed, + 0xd7, 0x0a, 0xcc, 0x35, 0xa2, 0xfb, 0x2a, 0x0f, 0x69, 0xf3, 0x29, 0x79, 0x96, 0xca, 0xe9, 0x41, + 0xbf, 0x38, 0x17, 0x23, 0xe0, 0xb8, 0x32, 0xea, 0xd7, 0x61, 0xe5, 0xe8, 0xc5, 0x1f, 0xc3, 0xa3, + 0x7e, 0x9c, 0x49, 0xd4, 0x20, 0xf7, 0x28, 0x45, 0x35, 0x58, 0x36, 0x9a, 0x96, 0xed, 0x92, 0x3b, + 0x56, 0xdb, 0xb2, 0x1f, 0x58, 0x1b, 0x76, 0xa7, 0x43, 0x2c, 0x2a, 0x7e, 0xca, 0x99, 0xad, 0x3c, + 0x2f, 0xa5, 0x2c, 0xef, 0x8c, 0x63, 0xc2, 0xe3, 0xe7, 0xf2, 0xef, 0x91, 0xd4, 0x35, 0x74, 0xca, + 0x77, 0x3f, 0xd6, 0x76, 0xa9, 0x71, 0x2a, 0x96, 0xa3, 0xb1, 0xff, 0x3b, 0x32, 0x9f, 0xc6, 0xff, + 0x1d, 0x61, 0xc2, 0x93, 0x3d, 0x22, 0xe1, 0xf9, 0x12, 0x73, 0xc3, 0x26, 0xd9, 0xd3, 0xdc, 0xb6, + 0xc7, 0xfb, 0x57, 0x79, 0x71, 0xf2, 0xb7, 0x02, 0x22, 0x8e, 0xc6, 0x87, 0x33, 0xff, 0x99, 0x67, + 0x25, 0xf3, 0x57, 0xff, 0xa1, 0x40, 0x7e, 0xc3, 0xb6, 0x1a, 0x06, 0xff, 0xa8, 0x72, 0x09, 0xb2, + 0xb4, 0xe7, 0x04, 0xa7, 0x24, 0xd8, 0xdf, 0xec, 0x7e, 0xcf, 0x21, 0x9f, 0xf4, 0x8b, 0x0b, 0x21, + 0x23, 0x23, 0x60, 0xce, 0x8a, 0x6e, 0xb2, 0xed, 0xd4, 0xa8, 0xef, 0xc9, 0x50, 0x74, 0x39, 0xda, + 0x4e, 0x46, 0xfd, 0xa4, 0x5f, 0x54, 0xa3, 0x7f, 0xfe, 0xca, 0xba, 0xed, 0x92, 0x72, 0xf7, 0x52, + 0x29, 0x94, 0x24, 0xb8, 0xb0, 0x94, 0xc1, 0x0e, 0x87, 0x4b, 0x34, 0x2f, 0x2c, 0x19, 0xc3, 0xc3, + 0x81, 0x39, 0x15, 0xcb, 0x51, 0x74, 0x01, 0x72, 0x1d, 0xe2, 0x79, 0x2c, 0xe3, 0x1c, 0xfa, 0x42, + 0xbc, 0x27, 0xc8, 0x38, 0x18, 0x57, 0xbf, 0x9f, 0x81, 0x85, 0x6d, 0x62, 0x11, 0xd7, 0xd0, 0x05, + 0x18, 0xba, 0x01, 0xc8, 0xae, 0x7b, 0xc4, 0xed, 0x92, 0xc6, 0xb6, 0xf8, 0xcf, 0x90, 0x45, 0x1c, + 0xb6, 0xe6, 0x4c, 0xd4, 0x41, 0xbe, 0x3d, 0xc2, 0x81, 0xc7, 0xcc, 0x42, 0x6f, 0x2b, 0x00, 0x7a, + 0xb0, 0x18, 0x4f, 0x56, 0xe1, 0x27, 0xd9, 0xc7, 0x0c, 0x2d, 0x15, 0x6d, 0x65, 0x48, 0xf2, 0x70, + 0x0c, 0x1b, 0xed, 0xc1, 0xd9, 0x03, 0xd7, 0x20, 0x56, 0xc3, 0xec, 0x6d, 0x12, 0x4f, 0x77, 0x0d, + 0x87, 0x46, 0xb5, 0xf7, 0x17, 0xe4, 0xe4, 0xb3, 0x5b, 0xa3, 0x2c, 0x78, 0xdc, 0x3c, 0x66, 0x25, + 0xdf, 0x23, 0x07, 0xbe, 0x79, 0xcd, 0x75, 0x6d, 0x77, 0x2f, 0x61, 0xed, 0xd0, 0x4a, 0x77, 0x46, + 0x38, 0xf0, 0x98, 0x59, 0xea, 0x0e, 0x0c, 0xc5, 0x32, 0xf4, 0x15, 0x58, 0x68, 0xba, 0xb6, 0xef, + 0xc8, 0x18, 0x1b, 0x94, 0x47, 0x67, 0x06, 0xfd, 0xe2, 0xc2, 0x76, 0x7c, 0x00, 0x27, 0xf9, 0xd4, + 0xcb, 0x10, 0x7c, 0xa1, 0x8f, 0x7f, 0xc3, 0x57, 0x9e, 0xfc, 0x0d, 0xbf, 0x52, 0x7a, 0xf4, 0x78, + 0xf5, 0xd4, 0x07, 0x8f, 0x57, 0x4f, 0x7d, 0xf4, 0x78, 0xf5, 0xd4, 0xf7, 0x06, 0xab, 0xca, 0xa3, + 0xc1, 0xaa, 0xf2, 0xc1, 0x60, 0x55, 0xf9, 0x68, 0xb0, 0xaa, 0xfc, 0x73, 0xb0, 0xaa, 0x3c, 0xfc, + 0xd7, 0xea, 0xa9, 0x57, 0x67, 0x03, 0xab, 0xff, 0x37, 0x00, 0x00, 0xff, 0xff, 0x0c, 0xce, 0xe4, + 0x51, 0x38, 0x2c, 0x00, 0x00, } func (m *AppCluster) Marshal() (dAtA []byte, err error) { @@ -2334,6 +2401,30 @@ func (m *AppTemplateHelmTemplate) MarshalToSizedBuffer(dAtA []byte) (int, error) _ = i var l int _ = l + if m.KubernetesAPIs != nil { + { + size, err := m.KubernetesAPIs.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + } + if m.KubernetesVersion != nil { + { + size, err := m.KubernetesVersion.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + } if len(m.ValuesFrom) > 0 { for iNdEx := len(m.ValuesFrom) - 1; iNdEx >= 0; iNdEx-- { { @@ -2655,6 +2746,42 @@ func (m *AppTemplateValuesDownwardAPIItem) MarshalToSizedBuffer(dAtA []byte) (in _ = i var l int _ = l + if m.KubernetesAPIs != nil { + { + size, err := m.KubernetesAPIs.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + } + if m.KappControllerVersion != nil { + { + size, err := m.KappControllerVersion.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + if m.KubernetesVersion != nil { + { + size, err := m.KubernetesVersion.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } i -= len(m.FieldPath) copy(dAtA[i:], m.FieldPath) i = encodeVarintGenerated(dAtA, i, uint64(len(m.FieldPath))) @@ -2936,6 +3063,66 @@ func (m *GenericStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *KubernetesAPIs) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *KubernetesAPIs) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *KubernetesAPIs) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.GroupVersions) > 0 { + for iNdEx := len(m.GroupVersions) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.GroupVersions[iNdEx]) + copy(dAtA[i:], m.GroupVersions[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.GroupVersions[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *Version) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Version) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Version) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.Version) + copy(dAtA[i:], m.Version) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Version))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { offset -= sovGenerated(v) base := offset @@ -3383,6 +3570,14 @@ func (m *AppTemplateHelmTemplate) Size() (n int) { n += 1 + l + sovGenerated(uint64(l)) } } + if m.KubernetesVersion != nil { + l = m.KubernetesVersion.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if m.KubernetesAPIs != nil { + l = m.KubernetesAPIs.Size() + n += 1 + l + sovGenerated(uint64(l)) + } return n } @@ -3504,6 +3699,18 @@ func (m *AppTemplateValuesDownwardAPIItem) Size() (n int) { n += 1 + l + sovGenerated(uint64(l)) l = len(m.FieldPath) n += 1 + l + sovGenerated(uint64(l)) + if m.KubernetesVersion != nil { + l = m.KubernetesVersion.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if m.KappControllerVersion != nil { + l = m.KappControllerVersion.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if m.KubernetesAPIs != nil { + l = m.KubernetesAPIs.Size() + n += 1 + l + sovGenerated(uint64(l)) + } return n } @@ -3611,6 +3818,32 @@ func (m *GenericStatus) Size() (n int) { return n } +func (m *KubernetesAPIs) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.GroupVersions) > 0 { + for _, s := range m.GroupVersions { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *Version) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Version) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + func sovGenerated(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -3914,6 +4147,8 @@ func (this *AppTemplateHelmTemplate) String() string { `Namespace:` + fmt.Sprintf("%v", this.Namespace) + `,`, `Path:` + fmt.Sprintf("%v", this.Path) + `,`, `ValuesFrom:` + repeatedStringForValuesFrom + `,`, + `KubernetesVersion:` + strings.Replace(this.KubernetesVersion.String(), "Version", "Version", 1) + `,`, + `KubernetesAPIs:` + strings.Replace(this.KubernetesAPIs.String(), "KubernetesAPIs", "KubernetesAPIs", 1) + `,`, `}`, }, "") return s @@ -4010,6 +4245,9 @@ func (this *AppTemplateValuesDownwardAPIItem) String() string { s := strings.Join([]string{`&AppTemplateValuesDownwardAPIItem{`, `Name:` + fmt.Sprintf("%v", this.Name) + `,`, `FieldPath:` + fmt.Sprintf("%v", this.FieldPath) + `,`, + `KubernetesVersion:` + strings.Replace(this.KubernetesVersion.String(), "Version", "Version", 1) + `,`, + `KappControllerVersion:` + strings.Replace(this.KappControllerVersion.String(), "Version", "Version", 1) + `,`, + `KubernetesAPIs:` + strings.Replace(this.KubernetesAPIs.String(), "KubernetesAPIs", "KubernetesAPIs", 1) + `,`, `}`, }, "") return s @@ -4088,6 +4326,26 @@ func (this *GenericStatus) String() string { }, "") return s } +func (this *KubernetesAPIs) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&KubernetesAPIs{`, + `GroupVersions:` + fmt.Sprintf("%v", this.GroupVersions) + `,`, + `}`, + }, "") + return s +} +func (this *Version) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&Version{`, + `Version:` + fmt.Sprintf("%v", this.Version) + `,`, + `}`, + }, "") + return s +} func valueToStringGenerated(v interface{}) string { rv := reflect.ValueOf(v) if rv.IsNil() { @@ -7608,6 +7866,78 @@ func (m *AppTemplateHelmTemplate) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field KubernetesVersion", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.KubernetesVersion == nil { + m.KubernetesVersion = &Version{} + } + if err := m.KubernetesVersion.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field KubernetesAPIs", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.KubernetesAPIs == nil { + m.KubernetesAPIs = &KubernetesAPIs{} + } + if err := m.KubernetesAPIs.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -8396,6 +8726,114 @@ func (m *AppTemplateValuesDownwardAPIItem) Unmarshal(dAtA []byte) error { } m.FieldPath = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field KubernetesVersion", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.KubernetesVersion == nil { + m.KubernetesVersion = &Version{} + } + if err := m.KubernetesVersion.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field KappControllerVersion", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.KappControllerVersion == nil { + m.KappControllerVersion = &Version{} + } + if err := m.KappControllerVersion.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field KubernetesAPIs", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.KubernetesAPIs == nil { + m.KubernetesAPIs = &KubernetesAPIs{} + } + if err := m.KubernetesAPIs.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -9258,6 +9696,170 @@ func (m *GenericStatus) Unmarshal(dAtA []byte) error { } return nil } +func (m *KubernetesAPIs) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: KubernetesAPIs: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: KubernetesAPIs: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GroupVersions", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.GroupVersions = append(m.GroupVersions, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Version) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Version: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Version: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Version = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipGenerated(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/pkg/apis/kappctrl/v1alpha1/generated.proto b/pkg/apis/kappctrl/v1alpha1/generated.proto index d7ae2d2e6..f607dcd02 100644 --- a/pkg/apis/kappctrl/v1alpha1/generated.proto +++ b/pkg/apis/kappctrl/v1alpha1/generated.proto @@ -331,6 +331,13 @@ message AppTemplateHelmTemplate { // One or more secrets, config maps, paths that provide values (optional) repeated AppTemplateValuesSource valuesFrom = 4; + + // Optional: Get Kubernetes version, defaults (empty) to retrieving the version from the cluster. + // Can be manually overridden to a value instead. + optional Version kubernetesVersion = 5; + + // Optional: Use kubernetes group/versions resources available in the live cluster + optional KubernetesAPIs kubernetesAPIs = 6; } // TODO implement jsonnet @@ -387,6 +394,18 @@ message AppTemplateValuesDownwardAPIItem { // Required: Selects a field of the app: only annotations, labels, uid, name and namespace are supported. optional string fieldPath = 2; + + // Optional: Get running Kubernetes version from cluster, defaults (empty) to retrieving the version from the cluster. + // Can be manually supplied instead. + optional Version kubernetesVersion = 3; + + // Optional: Get running KappController version, defaults (empty) to retrieving the current running version.. + // Can be manually supplied instead. + optional Version kappControllerVersion = 4; + + // Optional: Get running KubernetesAPIs from cluster, defaults (empty) to retrieving the APIs from the cluster. + // Can be manually supplied instead, e.g ["group/version", "group2/version2"] + optional KubernetesAPIs kubernetesAPIs = 5; } // +k8s:openapi-gen=true @@ -463,3 +482,13 @@ message GenericStatus { optional string usefulErrorMessage = 4; } +// +k8s:openapi-gen=true +message KubernetesAPIs { + repeated string groupVersions = 1; +} + +// +k8s:openapi-gen=true +message Version { + optional string version = 1; +} + diff --git a/pkg/apis/kappctrl/v1alpha1/types_template.go b/pkg/apis/kappctrl/v1alpha1/types_template.go index 58c5cb957..1e9470d4d 100644 --- a/pkg/apis/kappctrl/v1alpha1/types_template.go +++ b/pkg/apis/kappctrl/v1alpha1/types_template.go @@ -53,6 +53,11 @@ type AppTemplateHelmTemplate struct { Path string `json:"path,omitempty" protobuf:"bytes,3,opt,name=path"` // One or more secrets, config maps, paths that provide values (optional) ValuesFrom []AppTemplateValuesSource `json:"valuesFrom,omitempty" protobuf:"bytes,4,rep,name=valuesFrom"` + // Optional: Get Kubernetes version, defaults (empty) to retrieving the version from the cluster. + // Can be manually overridden to a value instead. + KubernetesVersion *Version `json:"kubernetesVersion,omitempty" protobuf:"bytes,5,opt,name=kubernetesVersion"` + // Optional: Use kubernetes group/versions resources available in the live cluster + KubernetesAPIs *KubernetesAPIs `json:"kubernetesAPIs,omitempty" protobuf:"bytes,6,opt,name=kubernetesAPIs"` } // +k8s:openapi-gen=true @@ -68,16 +73,35 @@ type AppTemplateValuesSourceRef struct { Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"` } +// +k8s:openapi-gen=true +type AppTemplateValuesDownwardAPI struct { + Items []AppTemplateValuesDownwardAPIItem `json:"items,omitempty" protobuf:"bytes,1,opt,name=items"` +} + // +k8s:openapi-gen=true type AppTemplateValuesDownwardAPIItem struct { Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"` // Required: Selects a field of the app: only annotations, labels, uid, name and namespace are supported. FieldPath string `json:"fieldPath,omitempty" protobuf:"bytes,2,opt,name=fieldPath"` + // Optional: Get running Kubernetes version from cluster, defaults (empty) to retrieving the version from the cluster. + // Can be manually supplied instead. + KubernetesVersion *Version `json:"kubernetesVersion,omitempty" protobuf:"bytes,3,opt,name=kubernetesVersion"` + // Optional: Get running KappController version, defaults (empty) to retrieving the current running version.. + // Can be manually supplied instead. + KappControllerVersion *Version `json:"kappControllerVersion,omitempty" protobuf:"bytes,4,opt,name=kappControllerVersion"` + // Optional: Get running KubernetesAPIs from cluster, defaults (empty) to retrieving the APIs from the cluster. + // Can be manually supplied instead, e.g ["group/version", "group2/version2"] + KubernetesAPIs *KubernetesAPIs `json:"kubernetesAPIs,omitempty" protobuf:"bytes,5,opt,name=kubernetesAPIs"` } // +k8s:openapi-gen=true -type AppTemplateValuesDownwardAPI struct { - Items []AppTemplateValuesDownwardAPIItem `json:"items,omitempty" protobuf:"bytes,1,opt,name=items"` +type Version struct { + Version string `json:"version,omitempty" protobuf:"bytes,1,opt,name=version"` +} + +// +k8s:openapi-gen=true +type KubernetesAPIs struct { + GroupVersions []string `json:"groupVersions,omitempty" protobuf:"bytes,1,opt,name=groupVersions"` } // TODO implement kustomize diff --git a/pkg/apis/kappctrl/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/kappctrl/v1alpha1/zz_generated.deepcopy.go index c8d42c15d..b51afa929 100644 --- a/pkg/apis/kappctrl/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/kappctrl/v1alpha1/zz_generated.deepcopy.go @@ -731,6 +731,16 @@ func (in *AppTemplateHelmTemplate) DeepCopyInto(out *AppTemplateHelmTemplate) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.KubernetesVersion != nil { + in, out := &in.KubernetesVersion, &out.KubernetesVersion + *out = new(Version) + **out = **in + } + if in.KubernetesAPIs != nil { + in, out := &in.KubernetesAPIs, &out.KubernetesAPIs + *out = new(KubernetesAPIs) + (*in).DeepCopyInto(*out) + } return } @@ -892,7 +902,9 @@ func (in *AppTemplateValuesDownwardAPI) DeepCopyInto(out *AppTemplateValuesDownw if in.Items != nil { in, out := &in.Items, &out.Items *out = make([]AppTemplateValuesDownwardAPIItem, len(*in)) - copy(*out, *in) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } return } @@ -910,6 +922,21 @@ func (in *AppTemplateValuesDownwardAPI) DeepCopy() *AppTemplateValuesDownwardAPI // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AppTemplateValuesDownwardAPIItem) DeepCopyInto(out *AppTemplateValuesDownwardAPIItem) { *out = *in + if in.KubernetesVersion != nil { + in, out := &in.KubernetesVersion, &out.KubernetesVersion + *out = new(Version) + **out = **in + } + if in.KappControllerVersion != nil { + in, out := &in.KappControllerVersion, &out.KappControllerVersion + *out = new(Version) + **out = **in + } + if in.KubernetesAPIs != nil { + in, out := &in.KubernetesAPIs, &out.KubernetesAPIs + *out = new(KubernetesAPIs) + (*in).DeepCopyInto(*out) + } return } @@ -1087,3 +1114,40 @@ func (in *KappDeployStatus) DeepCopy() *KappDeployStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubernetesAPIs) DeepCopyInto(out *KubernetesAPIs) { + *out = *in + if in.GroupVersions != nil { + in, out := &in.GroupVersions, &out.GroupVersions + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesAPIs. +func (in *KubernetesAPIs) DeepCopy() *KubernetesAPIs { + if in == nil { + return nil + } + out := new(KubernetesAPIs) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Version) DeepCopyInto(out *Version) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Version. +func (in *Version) DeepCopy() *Version { + if in == nil { + return nil + } + out := new(Version) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/apiserver/openapi/zz_generated.openapi.go b/pkg/apiserver/openapi/zz_generated.openapi.go index c214917b4..3197d0b72 100644 --- a/pkg/apiserver/openapi/zz_generated.openapi.go +++ b/pkg/apiserver/openapi/zz_generated.openapi.go @@ -50,6 +50,8 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1.AppTemplateValuesSourceRef": schema_pkg_apis_kappctrl_v1alpha1_AppTemplateValuesSourceRef(ref), "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1.AppTemplateYtt": schema_pkg_apis_kappctrl_v1alpha1_AppTemplateYtt(ref), "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1.Condition": schema_pkg_apis_kappctrl_v1alpha1_Condition(ref), + "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1.KubernetesAPIs": schema_pkg_apis_kappctrl_v1alpha1_KubernetesAPIs(ref), + "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1.Version": schema_pkg_apis_kappctrl_v1alpha1_Version(ref), "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apiserver/apis/datapackaging/v1alpha1.AppTemplateSpec": schema_apiserver_apis_datapackaging_v1alpha1_AppTemplateSpec(ref), "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apiserver/apis/datapackaging/v1alpha1.IncludedSoftware": schema_apiserver_apis_datapackaging_v1alpha1_IncludedSoftware(ref), "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apiserver/apis/datapackaging/v1alpha1.Maintainer": schema_apiserver_apis_datapackaging_v1alpha1_Maintainer(ref), @@ -1154,11 +1156,23 @@ func schema_pkg_apis_kappctrl_v1alpha1_AppTemplateHelmTemplate(ref common.Refere }, }, }, + "kubernetesVersion": { + SchemaProps: spec.SchemaProps{ + Description: "Optional: Get Kubernetes version, defaults (empty) to retrieving the version from the cluster. Can be manually overridden to a value instead.", + Ref: ref("github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1.Version"), + }, + }, + "kubernetesAPIs": { + SchemaProps: spec.SchemaProps{ + Description: "Optional: Use kubernetes group/versions resources available in the live cluster", + Ref: ref("github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1.KubernetesAPIs"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1.AppTemplateValuesSource"}, + "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1.AppTemplateValuesSource", "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1.KubernetesAPIs", "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1.Version"}, } } @@ -1352,9 +1366,29 @@ func schema_pkg_apis_kappctrl_v1alpha1_AppTemplateValuesDownwardAPIItem(ref comm Format: "", }, }, + "kubernetesVersion": { + SchemaProps: spec.SchemaProps{ + Description: "Optional: Get running Kubernetes version from cluster, defaults (empty) to retrieving the version from the cluster. Can be manually supplied instead.", + Ref: ref("github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1.Version"), + }, + }, + "kappControllerVersion": { + SchemaProps: spec.SchemaProps{ + Description: "Optional: Get running KappController version, defaults (empty) to retrieving the current running version.. Can be manually supplied instead.", + Ref: ref("github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1.Version"), + }, + }, + "kubernetesAPIs": { + SchemaProps: spec.SchemaProps{ + Description: "Optional: Get running KubernetesAPIs from cluster, defaults (empty) to retrieving the APIs from the cluster. Can be manually supplied instead, e.g [\"group/version\", \"group2/version2\"]", + Ref: ref("github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1.KubernetesAPIs"), + }, + }, }, }, }, + Dependencies: []string{ + "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1.KubernetesAPIs", "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1.Version"}, } } @@ -1530,6 +1564,50 @@ func schema_pkg_apis_kappctrl_v1alpha1_Condition(ref common.ReferenceCallback) c } } +func schema_pkg_apis_kappctrl_v1alpha1_KubernetesAPIs(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "groupVersions": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_kappctrl_v1alpha1_Version(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "version": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + func schema_apiserver_apis_datapackaging_v1alpha1_AppTemplateSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/pkg/app/app.go b/pkg/app/app.go index f8a6d88fe..f69626a3e 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -7,15 +7,24 @@ import ( "sync" "github.com/go-logr/logr" + "github.com/k14s/semver/v4" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/deploy" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/fetch" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/metrics" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/reftracker" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/template" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/yaml" ) +// ComponentInfo provides information about components of the system required by templating stage +type ComponentInfo interface { + KappControllerVersion() (semver.Version, error) + KubernetesVersion(serviceAccountName string, specCluster *v1alpha1.AppCluster, objMeta *metav1.ObjectMeta) (semver.Version, error) + KubernetesAPIs() ([]string, error) +} + type Hooks struct { BlockDeletion func() error UnblockDeletion func() error @@ -31,6 +40,10 @@ type App struct { fetchFactory fetch.Factory templateFactory template.Factory deployFactory deploy.Factory + compInfo ComponentInfo + + memoizedKubernetesVersion string + memoizedKubernetesAPIs []string log logr.Logger appMetrics *metrics.AppMetrics @@ -42,11 +55,11 @@ type App struct { func NewApp(app v1alpha1.App, hooks Hooks, fetchFactory fetch.Factory, templateFactory template.Factory, - deployFactory deploy.Factory, log logr.Logger, appMetrics *metrics.AppMetrics) *App { + deployFactory deploy.Factory, log logr.Logger, appMetrics *metrics.AppMetrics, compInfo ComponentInfo) *App { return &App{app: app, appPrev: *(app.DeepCopy()), hooks: hooks, fetchFactory: fetchFactory, templateFactory: templateFactory, - deployFactory: deployFactory, log: log, appMetrics: appMetrics} + deployFactory: deployFactory, log: log, appMetrics: appMetrics, compInfo: compInfo} } func (a *App) Name() string { return a.app.Name } diff --git a/pkg/app/app_deploy.go b/pkg/app/app_deploy.go index 38ae329f9..bb5fec26b 100644 --- a/pkg/app/app_deploy.go +++ b/pkg/app/app_deploy.go @@ -9,6 +9,7 @@ import ( "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1" ctldep "github.com/vmware-tanzu/carvel-kapp-controller/pkg/deploy" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/exec" + "github.com/vmware-tanzu/carvel-kapp-controller/pkg/kubeconfig" ) func (a *App) deploy(tplOutput string, changedFunc func(exec.CmdRunResult)) exec.CmdRunResult { @@ -134,10 +135,9 @@ func (a *App) trySaveMetadata(kapp *ctldep.Kapp) { } func (a *App) newKapp(kapp v1alpha1.AppDeployKapp, cancelCh chan struct{}) (*ctldep.Kapp, error) { - genericOpts := ctldep.GenericOpts{Name: a.app.Name, Namespace: a.app.Namespace} return a.deployFactory.NewKapp(kapp, a.app.Spec.ServiceAccountName, - a.app.Spec.Cluster, genericOpts, cancelCh) + a.app.Spec.Cluster, cancelCh, kubeconfig.AccessLocation{Name: a.app.Name, Namespace: a.app.Namespace}) } type cancelCondition func(v1alpha1.App) bool diff --git a/pkg/app/app_factory.go b/pkg/app/app_factory.go index 63d208d64..e0f2c1f5a 100644 --- a/pkg/app/app_factory.go +++ b/pkg/app/app_factory.go @@ -11,6 +11,7 @@ import ( "github.com/vmware-tanzu/carvel-kapp-controller/pkg/deploy" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/exec" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/fetch" + "github.com/vmware-tanzu/carvel-kapp-controller/pkg/kubeconfig" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/metrics" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/template" vendirconf "github.com/vmware-tanzu/carvel-vendir/pkg/vendir/config" @@ -26,7 +27,8 @@ type CRDAppFactory struct { VendirConfigHook func(vendirconf.Config) vendirconf.Config KbldAllowBuild bool CmdRunner exec.CmdRunner - DeployFactory deploy.Factory + Kubeconf *kubeconfig.Kubeconfig + CompInfo ComponentInfo } // NewCRDApp creates a CRDApp injecting necessary dependencies. @@ -35,7 +37,9 @@ func (f *CRDAppFactory) NewCRDApp(app *kcv1alpha1.App, log logr.Logger) *CRDApp SkipTLSConfig: f.KcConfig, ConfigHook: f.VendirConfigHook, } + fetchFactory := fetch.NewFactory(f.CoreClient, vendirOpts, f.CmdRunner) templateFactory := template.NewFactory(f.CoreClient, fetchFactory, f.KbldAllowBuild, f.CmdRunner) - return NewCRDApp(app, log, f.AppMetrics, f.AppClient, fetchFactory, templateFactory, f.DeployFactory) + deployFactory := deploy.NewFactory(f.CoreClient, f.Kubeconf, f.KcConfig, f.CmdRunner, log) + return NewCRDApp(app, log, f.AppMetrics, f.AppClient, fetchFactory, templateFactory, deployFactory, f.CompInfo) } diff --git a/pkg/app/app_reconcile_test.go b/pkg/app/app_reconcile_test.go index b1dbefcc6..176a56b1c 100644 --- a/pkg/app/app_reconcile_test.go +++ b/pkg/app/app_reconcile_test.go @@ -8,12 +8,14 @@ import ( "reflect" "testing" + "github.com/k14s/semver/v4" "github.com/stretchr/testify/assert" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/client/clientset/versioned/fake" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/deploy" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/exec" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/fetch" + "github.com/vmware-tanzu/carvel-kapp-controller/pkg/kubeconfig" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/metrics" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/template" corev1 "k8s.io/api/core/v1" @@ -44,9 +46,9 @@ func Test_NoInspectReconcile_IfNoDeployAttempted(t *testing.T) { kappcs := fake.NewSimpleClientset() fetchFac := fetch.NewFactory(k8scs, fetch.VendirOpts{}, exec.NewPlainCmdRunner()) tmpFac := template.NewFactory(k8scs, fetchFac, false, exec.NewPlainCmdRunner()) - deployFac := deploy.NewFactory(k8scs, nil, exec.NewPlainCmdRunner(), log) + deployFac := deploy.NewFactory(k8scs, kubeconfig.NewKubeconfig(k8scs, log), nil, exec.NewPlainCmdRunner(), log) - crdApp := NewCRDApp(&app, log, appMetrics, kappcs, fetchFac, tmpFac, deployFac) + crdApp := NewCRDApp(&app, log, appMetrics, kappcs, fetchFac, tmpFac, deployFac, FakeComponentInfo{}) _, err := crdApp.Reconcile(false) assert.Nil(t, err, "unexpected error with reconciling", err) @@ -110,9 +112,9 @@ func Test_NoInspectReconcile_IfInspectNotEnabled(t *testing.T) { kappcs := fake.NewSimpleClientset() fetchFac := fetch.NewFactory(k8scs, fetch.VendirOpts{}, exec.NewPlainCmdRunner()) tmpFac := template.NewFactory(k8scs, fetchFac, false, exec.NewPlainCmdRunner()) - deployFac := deploy.NewFactory(k8scs, nil, exec.NewPlainCmdRunner(), log) + deployFac := deploy.NewFactory(k8scs, kubeconfig.NewKubeconfig(k8scs, log), nil, exec.NewPlainCmdRunner(), log) - crdApp := NewCRDApp(&app, log, appMetrics, kappcs, fetchFac, tmpFac, deployFac) + crdApp := NewCRDApp(&app, log, appMetrics, kappcs, fetchFac, tmpFac, deployFac, FakeComponentInfo{}) _, err := crdApp.Reconcile(false) assert.Nil(t, err, "unexpected error with reconciling", err) @@ -181,9 +183,9 @@ func Test_TemplateError_DisplayedInStatus_UsefulErrorMessageProperty(t *testing. kappcs := fake.NewSimpleClientset() fetchFac := fetch.NewFactory(k8scs, fetch.VendirOpts{}, exec.NewPlainCmdRunner()) tmpFac := template.NewFactory(k8scs, fetchFac, false, exec.NewPlainCmdRunner()) - deployFac := deploy.NewFactory(k8scs, nil, exec.NewPlainCmdRunner(), log) + deployFac := deploy.NewFactory(k8scs, kubeconfig.NewKubeconfig(k8scs, log), nil, exec.NewPlainCmdRunner(), log) - crdApp := NewCRDApp(&app, log, appMetrics, kappcs, fetchFac, tmpFac, deployFac) + crdApp := NewCRDApp(&app, log, appMetrics, kappcs, fetchFac, tmpFac, deployFac, FakeComponentInfo{}) _, err := crdApp.Reconcile(false) assert.Nil(t, err, "Unexpected error with reconciling", err) @@ -223,3 +225,27 @@ func Test_TemplateError_DisplayedInStatus_UsefulErrorMessageProperty(t *testing. reflect.DeepEqual(expectedStatus, crdApp.app.Status()), fmt.Sprintf("Status is not same:\nExpected:\n%#v\nGot:\n%#v\n", expectedStatus, crdApp.app.Status())) } + +type FakeComponentInfo struct { + KCVersion semver.Version + KCVersionCount *int + K8sVersion semver.Version + K8sVersionCount *int + K8sAPIs []string + K8sAPIsCount *int +} + +func (f FakeComponentInfo) KubernetesAPIs() ([]string, error) { + *f.K8sAPIsCount++ + return f.K8sAPIs, nil +} + +func (f FakeComponentInfo) KappControllerVersion() (semver.Version, error) { + *f.KCVersionCount++ + return f.KCVersion, nil +} + +func (f FakeComponentInfo) KubernetesVersion(serviceAccountName string, specCluster *v1alpha1.AppCluster, objMeta *metav1.ObjectMeta) (semver.Version, error) { + *f.K8sVersionCount++ + return f.K8sVersion, nil +} diff --git a/pkg/app/app_template.go b/pkg/app/app_template.go index 1d9e4a178..91b335aa5 100644 --- a/pkg/app/app_template.go +++ b/pkg/app/app_template.go @@ -26,20 +26,22 @@ func (a *App) template(dirPath string) exec.CmdRunResult { var result exec.CmdRunResult var isStream bool + additionalValues := a.buildDownwardAPIAdditionalValues() + for _, tpl := range a.app.Spec.Template { var template ctltpl.Template switch { case tpl.Ytt != nil: - template = a.templateFactory.NewYtt(*tpl.Ytt, appContext) + template = a.templateFactory.NewYtt(*tpl.Ytt, appContext, additionalValues) case tpl.Kbld != nil: template = a.templateFactory.NewKbld(*tpl.Kbld, appContext) case tpl.HelmTemplate != nil: - template = a.templateFactory.NewHelmTemplate(*tpl.HelmTemplate, appContext) + template = a.templateFactory.NewHelmTemplate(*tpl.HelmTemplate, appContext, additionalValues) case tpl.Sops != nil: template = a.templateFactory.NewSops(*tpl.Sops, appContext) case tpl.Cue != nil: - template = a.templateFactory.NewCue(*tpl.Cue, appContext) + template = a.templateFactory.NewCue(*tpl.Cue, appContext, additionalValues) default: result.AttachErrorf("%s", fmt.Errorf("Unsupported way to template")) return result @@ -69,3 +71,35 @@ func asPartialObjectMetadata(m v1alpha1.App) ctltpl.PartialObjectMetadata { }, } } + +func (a *App) buildDownwardAPIAdditionalValues() ctltpl.AdditionalDownwardAPIValues { + return ctltpl.AdditionalDownwardAPIValues{ + KubernetesVersion: func() (string, error) { + if a.memoizedKubernetesVersion == "" { + v, err := a.compInfo.KubernetesVersion(a.app.Spec.ServiceAccountName, a.app.Spec.Cluster, &a.app.ObjectMeta) + if err != nil { + return "", fmt.Errorf("Unable to get kubernetes version: %s", err) + } + a.memoizedKubernetesVersion = v.String() + } + return a.memoizedKubernetesVersion, nil + }, + KappControllerVersion: func() (string, error) { + v, err := a.compInfo.KappControllerVersion() + if err != nil { + return "", fmt.Errorf("Unable to get kapp-controller version: %s", err) + } + return v.String(), nil + }, + KubernetesAPIs: func() ([]string, error) { + if len(a.memoizedKubernetesAPIs) == 0 { + v, err := a.compInfo.KubernetesAPIs() + if err != nil { + return []string{}, fmt.Errorf("Unable to list all server apigroups/version: %s", err) + } + a.memoizedKubernetesAPIs = v + } + return a.memoizedKubernetesAPIs, nil + }, + } +} diff --git a/pkg/app/app_template_test.go b/pkg/app/app_template_test.go new file mode 100644 index 000000000..bcc770df9 --- /dev/null +++ b/pkg/app/app_template_test.go @@ -0,0 +1,74 @@ +// Copyright 2022 VMware, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package app + +import ( + "os" + "testing" + + "github.com/k14s/semver/v4" + "github.com/stretchr/testify/assert" + "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1" + "github.com/vmware-tanzu/carvel-kapp-controller/pkg/deploy" + "github.com/vmware-tanzu/carvel-kapp-controller/pkg/exec" + "github.com/vmware-tanzu/carvel-kapp-controller/pkg/fetch" + "github.com/vmware-tanzu/carvel-kapp-controller/pkg/kubeconfig" + "github.com/vmware-tanzu/carvel-kapp-controller/pkg/metrics" + "github.com/vmware-tanzu/carvel-kapp-controller/pkg/template" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + k8sfake "k8s.io/client-go/kubernetes/fake" + logf "sigs.k8s.io/controller-runtime/pkg/log" +) + +func Test_BuildAdditionalDownwardAPIValues_MemoizedCallCount(t *testing.T) { + log := logf.Log.WithName("kc") + + appEmpty := v1alpha1.App{ + ObjectMeta: metav1.ObjectMeta{ + Name: "simple-app", + Namespace: "default", + }, + Spec: v1alpha1.AppSpec{ + Fetch: []v1alpha1.AppFetch{}, + Template: []v1alpha1.AppTemplate{ + {Ytt: &v1alpha1.AppTemplateYtt{ValuesFrom: []v1alpha1.AppTemplateValuesSource{{DownwardAPI: &v1alpha1.AppTemplateValuesDownwardAPI{Items: []v1alpha1.AppTemplateValuesDownwardAPIItem{ + {KubernetesVersion: &v1alpha1.Version{}}, + {KappControllerVersion: &v1alpha1.Version{}}, + {KubernetesAPIs: &v1alpha1.KubernetesAPIs{}}}, + }}}}}, + {Ytt: &v1alpha1.AppTemplateYtt{ValuesFrom: []v1alpha1.AppTemplateValuesSource{{DownwardAPI: &v1alpha1.AppTemplateValuesDownwardAPI{Items: []v1alpha1.AppTemplateValuesDownwardAPIItem{ + {KubernetesVersion: &v1alpha1.Version{}}, + {KappControllerVersion: &v1alpha1.Version{}}, + {KubernetesAPIs: &v1alpha1.KubernetesAPIs{}}}, + }}}}}, + {HelmTemplate: &v1alpha1.AppTemplateHelmTemplate{ + KubernetesVersion: &v1alpha1.Version{}, + KubernetesAPIs: &v1alpha1.KubernetesAPIs{}, + }}, + }, + }, + } + + k8scs := k8sfake.NewSimpleClientset() + fetchFac := fetch.NewFactory(k8scs, fetch.VendirOpts{}, exec.NewPlainCmdRunner()) + tmpFac := template.NewFactory(k8scs, fetchFac, false, exec.NewPlainCmdRunner()) + deployFac := deploy.NewFactory(k8scs, kubeconfig.NewKubeconfig(k8scs, log), nil, exec.NewPlainCmdRunner(), log) + + k8sVersionCallCount, kcVersionCallCount, k8sAPIsCallCount := 0, 0, 0 + fakeInfo := FakeComponentInfo{K8sVersion: semver.MustParse("1.1.0"), KCVersion: semver.MustParse("2.0.0"), K8sAPIs: []string{"test", "test2"}, + K8sVersionCount: &k8sVersionCallCount, + K8sAPIsCount: &k8sAPIsCallCount, + KCVersionCount: &kcVersionCallCount, + } + app := NewApp(appEmpty, Hooks{}, fetchFac, tmpFac, deployFac, log, metrics.NewAppMetrics(), fakeInfo) + + dir, err := os.MkdirTemp("", "temp") + assert.NoError(t, err) + + app.template(dir) + + assert.Equal(t, 1, k8sVersionCallCount) + assert.Equal(t, 2, kcVersionCallCount) + assert.Equal(t, 1, k8sAPIsCallCount) +} diff --git a/pkg/app/app_test.go b/pkg/app/app_test.go index 0f4045caf..c0a4924ae 100644 --- a/pkg/app/app_test.go +++ b/pkg/app/app_test.go @@ -7,12 +7,14 @@ import ( "reflect" "testing" + "github.com/k14s/semver/v4" "github.com/stretchr/testify/assert" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1" apppkg "github.com/vmware-tanzu/carvel-kapp-controller/pkg/app" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/deploy" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/exec" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/fetch" + "github.com/vmware-tanzu/carvel-kapp-controller/pkg/kubeconfig" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/reftracker" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/template" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -59,9 +61,9 @@ func Test_SecretRefs_RetrievesAllSecretRefs(t *testing.T) { k8scs := k8sfake.NewSimpleClientset() fetchFac := fetch.NewFactory(k8scs, fetch.VendirOpts{}, exec.NewPlainCmdRunner()) tmpFac := template.NewFactory(k8scs, fetchFac, false, exec.NewPlainCmdRunner()) - deployFac := deploy.NewFactory(k8scs, nil, exec.NewPlainCmdRunner(), log) + deployFac := deploy.NewFactory(k8scs, kubeconfig.NewKubeconfig(k8scs, log), nil, exec.NewPlainCmdRunner(), log) - app := apppkg.NewApp(appWithRefs, apppkg.Hooks{}, fetchFac, tmpFac, deployFac, log, nil) + app := apppkg.NewApp(appWithRefs, apppkg.Hooks{}, fetchFac, tmpFac, deployFac, log, nil, FakeComponentInfo{}) out := app.SecretRefs() assert.Truef(t, reflect.DeepEqual(out, expected), "Expected: %s\nGot: %s\n", expected, out) @@ -83,9 +85,9 @@ func Test_SecretRefs_RetrievesNoSecretRefs_WhenNonePresent(t *testing.T) { k8scs := k8sfake.NewSimpleClientset() fetchFac := fetch.NewFactory(k8scs, fetch.VendirOpts{}, exec.NewPlainCmdRunner()) tmpFac := template.NewFactory(k8scs, fetchFac, false, exec.NewPlainCmdRunner()) - deployFac := deploy.NewFactory(k8scs, nil, exec.NewPlainCmdRunner(), log) + deployFac := deploy.NewFactory(k8scs, kubeconfig.NewKubeconfig(k8scs, log), nil, exec.NewPlainCmdRunner(), log) - app := apppkg.NewApp(appEmpty, apppkg.Hooks{}, fetchFac, tmpFac, deployFac, log, nil) + app := apppkg.NewApp(appEmpty, apppkg.Hooks{}, fetchFac, tmpFac, deployFac, log, nil, FakeComponentInfo{}) out := app.SecretRefs() assert.Equal(t, 0, len(out), "No SecretRefs to be returned") @@ -121,9 +123,9 @@ func Test_ConfigMapRefs_RetrievesAllConfigMapRefs(t *testing.T) { k8scs := k8sfake.NewSimpleClientset() fetchFac := fetch.NewFactory(k8scs, fetch.VendirOpts{}, exec.NewPlainCmdRunner()) tmpFac := template.NewFactory(k8scs, fetchFac, false, exec.NewPlainCmdRunner()) - deployFac := deploy.NewFactory(k8scs, nil, exec.NewPlainCmdRunner(), log) + deployFac := deploy.NewFactory(k8scs, kubeconfig.NewKubeconfig(k8scs, log), nil, exec.NewPlainCmdRunner(), log) - app := apppkg.NewApp(appWithRefs, apppkg.Hooks{}, fetchFac, tmpFac, deployFac, log, nil) + app := apppkg.NewApp(appWithRefs, apppkg.Hooks{}, fetchFac, tmpFac, deployFac, log, nil, FakeComponentInfo{}) out := app.ConfigMapRefs() assert.Truef(t, reflect.DeepEqual(out, expected), "Expected: %s\nGot: %s\n", expected, out) @@ -145,10 +147,28 @@ func Test_ConfigMapRefs_RetrievesNoConfigMapRefs_WhenNonePresent(t *testing.T) { k8scs := k8sfake.NewSimpleClientset() fetchFac := fetch.NewFactory(k8scs, fetch.VendirOpts{}, exec.NewPlainCmdRunner()) tmpFac := template.NewFactory(k8scs, fetchFac, false, exec.NewPlainCmdRunner()) - deployFac := deploy.NewFactory(k8scs, nil, exec.NewPlainCmdRunner(), log) + deployFac := deploy.NewFactory(k8scs, kubeconfig.NewKubeconfig(k8scs, log), nil, exec.NewPlainCmdRunner(), log) - app := apppkg.NewApp(appEmpty, apppkg.Hooks{}, fetchFac, tmpFac, deployFac, log, nil) + app := apppkg.NewApp(appEmpty, apppkg.Hooks{}, fetchFac, tmpFac, deployFac, log, nil, FakeComponentInfo{}) out := app.ConfigMapRefs() assert.Lenf(t, out, 0, "Expected: %s\nGot: %s\n", "No ConfigMapRefs to be returned", out) } + +type FakeComponentInfo struct { + KCVersion semver.Version + K8sVersion semver.Version + K8sAPIs []string +} + +func (f FakeComponentInfo) KubernetesAPIs() ([]string, error) { + return f.K8sAPIs, nil +} + +func (f FakeComponentInfo) KappControllerVersion() (semver.Version, error) { + return f.KCVersion, nil +} + +func (f FakeComponentInfo) KubernetesVersion(serviceAccountName string, specCluster *v1alpha1.AppCluster, objMeta *metav1.ObjectMeta) (semver.Version, error) { + return f.K8sVersion, nil +} diff --git a/pkg/app/crd_app.go b/pkg/app/crd_app.go index 9c897aaf2..d983cfde7 100644 --- a/pkg/app/crd_app.go +++ b/pkg/app/crd_app.go @@ -31,7 +31,7 @@ type CRDApp struct { // NewCRDApp creates new CRD app func NewCRDApp(appModel *kcv1alpha1.App, log logr.Logger, appMetrics *metrics.AppMetrics, appClient kcclient.Interface, fetchFactory fetch.Factory, - templateFactory template.Factory, deployFactory deploy.Factory) *CRDApp { + templateFactory template.Factory, deployFactory deploy.Factory, compInfo ComponentInfo) *CRDApp { crdApp := &CRDApp{appModel: appModel, log: log, appMetrics: appMetrics, appClient: appClient} @@ -40,7 +40,7 @@ func NewCRDApp(appModel *kcv1alpha1.App, log logr.Logger, appMetrics *metrics.Ap UnblockDeletion: crdApp.unblockDeletion, UpdateStatus: crdApp.updateStatus, WatchChanges: crdApp.watchChanges, - }, fetchFactory, templateFactory, deployFactory, log, appMetrics) + }, fetchFactory, templateFactory, deployFactory, log, appMetrics, compInfo) return crdApp } diff --git a/pkg/app/reconciler.go b/pkg/app/reconciler.go index d996a9d34..db454360d 100644 --- a/pkg/app/reconciler.go +++ b/pkg/app/reconciler.go @@ -29,16 +29,18 @@ type Reconciler struct { crdAppFactory CRDAppFactory appRefTracker *reftracker.AppRefTracker appUpdateStatus *reftracker.AppUpdateStatus + componentInfo ComponentInfo } // NewReconciler constructs new Reconciler. func NewReconciler(appClient kcclient.Interface, log logr.Logger, crdAppFactory CRDAppFactory, - appRefTracker *reftracker.AppRefTracker, appUpdateStatus *reftracker.AppUpdateStatus) *Reconciler { + appRefTracker *reftracker.AppRefTracker, appUpdateStatus *reftracker.AppUpdateStatus, componentInfo ComponentInfo) *Reconciler { return &Reconciler{appClient: appClient, log: log, crdAppFactory: crdAppFactory, appRefTracker: appRefTracker, appUpdateStatus: appUpdateStatus, + componentInfo: componentInfo, } } diff --git a/pkg/app/reconciler_test.go b/pkg/app/reconciler_test.go index 07303809b..ba651eb60 100644 --- a/pkg/app/reconciler_test.go +++ b/pkg/app/reconciler_test.go @@ -37,7 +37,7 @@ func Test_AppRefTracker_HasAppRemovedForSecrets_ThatAreNoLongerUsedByApp(t *test appKey := reftracker.NewAppKey(app.Name, app.Namespace) appRefTracker.ReconcileRefs(refKeyMap, appKey) - ar := apppkg.NewReconciler(nil, nil, apppkg.CRDAppFactory{}, appRefTracker, nil) + ar := apppkg.NewReconciler(nil, nil, apppkg.CRDAppFactory{}, appRefTracker, nil, FakeComponentInfo{}) // This map represents the secrets the App has on its spec refMap := map[reftracker.RefKey]struct{}{ @@ -89,7 +89,7 @@ func Test_AppRefTracker_HasNoAppsRemoved_WhenRefsRemainSame(t *testing.T) { appKey := reftracker.NewAppKey(app.Name, app.Namespace) appRefTracker.ReconcileRefs(refKeyMap, appKey) - ar := apppkg.NewReconciler(nil, nil, apppkg.CRDAppFactory{}, appRefTracker, nil) + ar := apppkg.NewReconciler(nil, nil, apppkg.CRDAppFactory{}, appRefTracker, nil, FakeComponentInfo{}) // This map represents the secrets the App has // on its spec diff --git a/pkg/componentinfo/component_info.go b/pkg/componentinfo/component_info.go new file mode 100644 index 000000000..ac2a7fde3 --- /dev/null +++ b/pkg/componentinfo/component_info.go @@ -0,0 +1,83 @@ +// Copyright 2022 VMware, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package componentinfo + +import ( + "fmt" + + "github.com/k14s/semver/v4" + "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1" + "github.com/vmware-tanzu/carvel-kapp-controller/pkg/kubeconfig" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" +) + +// ComponentInfo provides information about components of system +type ComponentInfo struct { + coreClient kubernetes.Interface + clusterAccess *kubeconfig.Kubeconfig + kappControllerVersion string +} + +// NewComponentInfo returns a ComponentInfo +func NewComponentInfo(coreClient kubernetes.Interface, clusterAccess *kubeconfig.Kubeconfig, kappControllerVersion string) *ComponentInfo { + return &ComponentInfo{coreClient: coreClient, clusterAccess: clusterAccess, kappControllerVersion: kappControllerVersion} +} + +// KappControllerVersion returns the running KC version +func (ci *ComponentInfo) KappControllerVersion() (semver.Version, error) { + v, err := semver.ParseTolerant(ci.kappControllerVersion) + if err != nil { + return semver.Version{}, err + } + return v, nil +} + +// KubernetesVersion returns the running K8s version depending on AppSpec +// If AppSpec points to external cluster, we use that k8s version instead +func (ci *ComponentInfo) KubernetesVersion(serviceAccountName string, specCluster *v1alpha1.AppCluster, objMeta *metav1.ObjectMeta) (semver.Version, error) { + switch { + case len(serviceAccountName) > 0: + v, err := ci.coreClient.Discovery().ServerVersion() + if err != nil { + return semver.Version{}, err + } + return semver.ParseTolerant(v.String()) + + case specCluster != nil: + accessInfo, err := ci.clusterAccess.ClusterAccess(serviceAccountName, specCluster, kubeconfig.AccessLocation{Name: objMeta.Name, Namespace: objMeta.Namespace}) + if err != nil { + return semver.Version{}, err + } + config, err := clientcmd.RESTConfigFromKubeConfig([]byte(accessInfo.Kubeconfig.AsYAML())) + if err != nil { + return semver.Version{}, err + } + + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + return semver.Version{}, err + } + + v, err := clientset.Discovery().ServerVersion() + if err != nil { + return semver.Version{}, err + } + return semver.ParseTolerant(v.String()) + + default: + return semver.Version{}, fmt.Errorf("Expected service account or cluster specified") + } +} + +// KubernetesAPIs returns the available kubernetes Group/Version resources +func (ci *ComponentInfo) KubernetesAPIs() ([]string, error) { + groups, err := ci.coreClient.Discovery().ServerGroups() + if err != nil { + return []string{}, err + } + + return metav1.ExtractGroupVersions(groups), nil +} diff --git a/pkg/deploy/factory.go b/pkg/deploy/factory.go index a465578d8..a0526e3ec 100644 --- a/pkg/deploy/factory.go +++ b/pkg/deploy/factory.go @@ -4,15 +4,11 @@ package deploy import ( - "fmt" - "github.com/go-logr/logr" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/exec" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/version" + "github.com/vmware-tanzu/carvel-kapp-controller/pkg/kubeconfig" "k8s.io/client-go/kubernetes" - "k8s.io/client-go/tools/clientcmd" ) // Factory allows to build various deployers. @@ -22,10 +18,8 @@ type Factory struct { coreClient kubernetes.Interface kappConfig KappConfiguration - kubeconfigSecrets *KubeconfigSecrets - serviceAccounts *ServiceAccounts - - cmdRunner exec.CmdRunner + kubeconfig *kubeconfig.Kubeconfig + cmdRunner exec.CmdRunner } // KappConfiguration provides a way to inject shared kapp settings. @@ -34,69 +28,40 @@ type KappConfiguration interface { } // NewFactory returns deploy factory. -func NewFactory(coreClient kubernetes.Interface, +func NewFactory(coreClient kubernetes.Interface, kubeconfig *kubeconfig.Kubeconfig, kappConfig KappConfiguration, cmdRunner exec.CmdRunner, log logr.Logger) Factory { - return Factory{coreClient, kappConfig, - NewKubeconfigSecrets(coreClient), NewServiceAccounts(coreClient, log), cmdRunner} + return Factory{coreClient, kappConfig, kubeconfig, cmdRunner} } // NewKapp configures and returns a deployer of type Kapp func (f Factory) NewKapp(opts v1alpha1.AppDeployKapp, saName string, - clusterOpts *v1alpha1.AppCluster, genericOpts GenericOpts, cancelCh chan struct{}) (*Kapp, error) { + clusterOpts *v1alpha1.AppCluster, cancelCh chan struct{}, location kubeconfig.AccessLocation) (*Kapp, error) { - processedGenericOpts, err := f.processOpts(saName, clusterOpts, genericOpts) + clusterAccess, err := f.kubeconfig.ClusterAccess(saName, clusterOpts, location) if err != nil { return nil, err } + const suffix string = ".app" - return NewKapp(suffix, opts, processedGenericOpts, + return NewKapp(suffix, opts, clusterAccess, f.globalKappDeployRawOpts(), cancelCh, f.cmdRunner), nil } -// processOpts takes generic opts and a ServiceAccount Name, and returns a populated kubeconfig that can connect to a cluster. -// if the saName is empty then you'll connect to a cluster via the clusterOpts inside the genericOpts, otherwise you'll use the specified SA. -func (f Factory) processOpts(saName string, clusterOpts *v1alpha1.AppCluster, genericOpts GenericOpts) (ProcessedGenericOpts, error) { - var err error - var processedGenericOpts ProcessedGenericOpts - - switch { - case len(saName) > 0: - processedGenericOpts, err = f.serviceAccounts.Find(genericOpts, saName) - if err != nil { - return ProcessedGenericOpts{}, err - } - - case clusterOpts != nil: - processedGenericOpts, err = f.kubeconfigSecrets.Find(genericOpts, clusterOpts) - if err != nil { - return ProcessedGenericOpts{}, err - } - - default: - return ProcessedGenericOpts{}, fmt.Errorf("Expected service account or cluster specified") - } - return processedGenericOpts, nil -} - // NewKappPrivileged is used for package repositories where users aren't required to provide // a service account, so it will install resources using its own privileges. -func (f Factory) NewKappPrivilegedForPackageRepository(opts v1alpha1.AppDeployKapp, - genericOpts GenericOpts, cancelCh chan struct{}) (*Kapp, error) { +func (f Factory) NewKappPrivilegedForPackageRepository(opts v1alpha1.AppDeployKapp, clusterAccess kubeconfig.AccessInfo, cancelCh chan struct{}) (*Kapp, error) { const suffix string = ".pkgr" - pgo := ProcessedGenericOpts{ - Name: genericOpts.Name, - Namespace: genericOpts.Namespace, - // Just use the default service account. Mainly - // used for PackageRepos now so users do not need - // to specify serviceaccount via PackageRepo CR. + kconfAccess := kubeconfig.AccessInfo{ + Name: clusterAccess.Name, + Namespace: clusterAccess.Namespace, Kubeconfig: nil, DangerousUsePodServiceAccount: true, } - return NewKapp(suffix, opts, pgo, f.globalKappDeployRawOpts(), cancelCh, f.cmdRunner), nil + return NewKapp(suffix, opts, kconfAccess, f.globalKappDeployRawOpts(), cancelCh, f.cmdRunner), nil } func (f Factory) globalKappDeployRawOpts() []string { @@ -105,30 +70,3 @@ func (f Factory) globalKappDeployRawOpts() []string { } return nil } - -// GetClusterVersion returns the kubernetes API version for the cluster which has been supplied to kapp-controller via a kubeconfig -func (f Factory) GetClusterVersion(saName string, specCluster *v1alpha1.AppCluster, objMeta *metav1.ObjectMeta, log logr.Logger) (*version.Info, error) { - switch { - case len(saName) > 0: - return f.coreClient.Discovery().ServerVersion() - case specCluster != nil: - processedGenericOpts, err := f.processOpts(saName, specCluster, GenericOpts{Name: objMeta.Name, Namespace: objMeta.Namespace}) - if err != nil { - return nil, err - } - - config, err := clientcmd.RESTConfigFromKubeConfig([]byte(processedGenericOpts.Kubeconfig.AsYAML())) - if err != nil { - return nil, err - } - - clientset, err := kubernetes.NewForConfig(config) - if err != nil { - return nil, err - } - - return clientset.Discovery().ServerVersion() - default: - return nil, fmt.Errorf("Expected service account or cluster specified") - } -} diff --git a/pkg/deploy/interfaces.go b/pkg/deploy/interfaces.go index be0954bc2..9a1112500 100644 --- a/pkg/deploy/interfaces.go +++ b/pkg/deploy/interfaces.go @@ -16,16 +16,3 @@ type Deploy interface { Inspect() exec.CmdRunResult } - -type GenericOpts struct { - Name string - Namespace string -} - -type ProcessedGenericOpts struct { - Name string - Namespace string - - Kubeconfig *KubeconfigRestricted - DangerousUsePodServiceAccount bool -} diff --git a/pkg/deploy/kapp.go b/pkg/deploy/kapp.go index c13edd74f..44f3f6966 100644 --- a/pkg/deploy/kapp.go +++ b/pkg/deploy/kapp.go @@ -7,7 +7,6 @@ import ( "bytes" "errors" "fmt" - "gopkg.in/yaml.v2" "os" goexec "os/exec" "path/filepath" @@ -16,7 +15,9 @@ import ( "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/exec" + "github.com/vmware-tanzu/carvel-kapp-controller/pkg/kubeconfig" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/memdir" + "sigs.k8s.io/yaml" ) const ( @@ -28,7 +29,7 @@ const ( type Kapp struct { appSuffix string opts v1alpha1.AppDeployKapp - genericOpts ProcessedGenericOpts + clusterAccess kubeconfig.AccessInfo globalDeployRawOpts []string cancelCh chan struct{} cmdRunner exec.CmdRunner @@ -40,10 +41,10 @@ var _ Deploy = &Kapp{} // NewKapp takes the kapp yaml from spec.deploy.kapp as arg kapp, // additional info from the larger app resource (e.g. service account, name, namespace) as genericOpts, // and a cancel channel that gets passed through to the exec call that runs kapp. -func NewKapp(appSuffix string, opts v1alpha1.AppDeployKapp, genericOpts ProcessedGenericOpts, +func NewKapp(appSuffix string, opts v1alpha1.AppDeployKapp, clusterAccess kubeconfig.AccessInfo, globalDeployRawOpts []string, cancelCh chan struct{}, cmdRunner exec.CmdRunner) *Kapp { - return &Kapp{appSuffix, opts, genericOpts, globalDeployRawOpts, cancelCh, cmdRunner, nil} + return &Kapp{appSuffix, opts, clusterAccess, globalDeployRawOpts, cancelCh, cmdRunner, nil} } // Deploy takes the output from templating, and the app name, @@ -66,7 +67,7 @@ func (a *Kapp) Deploy(tplOutput string, startedApplyingFunc func(), return exec.NewCmdRunResultWithErr(err) } - args, env := a.addGenericArgs(args, a.genericOpts.Name+a.appSuffix) + args, env := a.addGenericArgs(args, a.clusterAccess.Name+a.appSuffix) cmd := goexec.Command("kapp", args...) cmd.Env = append(os.Environ(), env...) @@ -93,7 +94,7 @@ func (a *Kapp) Delete(startedApplyingFunc func(), changedFunc func(exec.CmdRunRe return exec.NewCmdRunResultWithErr(err) } - args, env := a.addGenericArgs(args, a.genericOpts.Name+a.appSuffix) + args, env := a.addGenericArgs(args, a.clusterAccess.Name+a.appSuffix) cmd := goexec.Command("kapp", args...) cmd.Env = append(os.Environ(), env...) @@ -123,7 +124,7 @@ func (a *Kapp) Inspect() exec.CmdRunResult { return exec.NewCmdRunResultWithErr(err) } - args, env := a.addGenericArgs(args, a.genericOpts.Name+a.appSuffix) + args, env := a.addGenericArgs(args, a.clusterAccess.Name+a.appSuffix) var stdoutBs, stderrBs bytes.Buffer @@ -200,7 +201,7 @@ func (a *Kapp) trackCmdOutput(cmd *goexec.Cmd, startedApplyingFunc func(), // This is the old naming schema for KC owned kapp apps. // The new convention is x.app for AppCRs / PKGIs and x.pkgr for PackageRepositories. -func (a *Kapp) oldManagedName() string { return a.genericOpts.Name + "-ctrl" } +func (a *Kapp) oldManagedName() string { return a.clusterAccess.Name + "-ctrl" } func (a *Kapp) addDeployArgs(args []string) ([]string, error) { if len(a.opts.IntoNs) > 0 { @@ -253,15 +254,15 @@ func (a *Kapp) addGenericArgs(args []string, appName string) ([]string, []string args = append(args, []string{"--app", appName}...) env := []string{} - if len(a.genericOpts.Namespace) > 0 { - args = append(args, []string{"--namespace", a.genericOpts.Namespace}...) + if len(a.clusterAccess.Namespace) > 0 { + args = append(args, []string{"--namespace", a.clusterAccess.Namespace}...) } switch { - case a.genericOpts.Kubeconfig != nil: - env = append(env, "KAPP_KUBECONFIG_YAML="+a.genericOpts.Kubeconfig.AsYAML()) + case a.clusterAccess.Kubeconfig != nil: + env = append(env, "KAPP_KUBECONFIG_YAML="+a.clusterAccess.Kubeconfig.AsYAML()) args = append(args, "--kubeconfig=/dev/null") // not used due to above env var - case a.genericOpts.DangerousUsePodServiceAccount: + case a.clusterAccess.DangerousUsePodServiceAccount: // do nothing default: panic("Internal inconsistency: Unknown kapp service account configuration") diff --git a/pkg/kubeconfig/kubeconfig.go b/pkg/kubeconfig/kubeconfig.go new file mode 100644 index 000000000..5c4386dc9 --- /dev/null +++ b/pkg/kubeconfig/kubeconfig.go @@ -0,0 +1,70 @@ +// Copyright 2022 VMware, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package kubeconfig + +import ( + "fmt" + + "github.com/go-logr/logr" + "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1" + "k8s.io/client-go/kubernetes" +) + +// Kubeconfig provides access to the kubernetes cluster +// It initializes the service-account token cache +type Kubeconfig struct { + kubeconfigSecrets *Secrets + serviceAccounts *ServiceAccounts + log logr.Logger +} + +// AccessLocation contains the name/namespace of the resource which provides cluster access +// for example, a service account has a name and namespace +type AccessLocation struct { + Name string + Namespace string +} + +// AccessInfo provides a kubernetes kubeconfig for use to access the cluster +type AccessInfo struct { + Name string + Namespace string + + Kubeconfig *Restricted + DangerousUsePodServiceAccount bool +} + +// NewKubeconfig creates a Kubeconfig with a new serviceaccount cache and kubeconfigsecrets +func NewKubeconfig(coreClient kubernetes.Interface, log logr.Logger) *Kubeconfig { + return &Kubeconfig{ + kubeconfigSecrets: NewKubeconfigSecrets(coreClient), + serviceAccounts: NewServiceAccounts(coreClient, log), + log: log, + } +} + +// ClusterAccess takes cluster info and a ServiceAccount Name, and returns a populated kubeconfig that can connect to a cluster. +// if the saName is empty then you'll connect to a cluster via the clusterOpts inside the genericOpts, otherwise you'll use the specified SA. +func (k Kubeconfig) ClusterAccess(saName string, clusterOpts *v1alpha1.AppCluster, accessLocation AccessLocation) (AccessInfo, error) { + var err error + var clusterAccessInfo AccessInfo + + switch { + case len(saName) > 0: + clusterAccessInfo, err = k.serviceAccounts.Find(accessLocation, saName) + if err != nil { + return AccessInfo{}, err + } + + case clusterOpts != nil: + clusterAccessInfo, err = k.kubeconfigSecrets.Find(accessLocation, clusterOpts) + if err != nil { + return AccessInfo{}, err + } + + default: + return AccessInfo{}, fmt.Errorf("Expected service account or cluster specified") + } + return clusterAccessInfo, nil +} diff --git a/pkg/deploy/kubeconfig_restricted.go b/pkg/kubeconfig/kubeconfig_restricted.go similarity index 88% rename from pkg/deploy/kubeconfig_restricted.go rename to pkg/kubeconfig/kubeconfig_restricted.go index f8f8608a6..8f11e0d69 100644 --- a/pkg/deploy/kubeconfig_restricted.go +++ b/pkg/kubeconfig/kubeconfig_restricted.go @@ -1,7 +1,7 @@ // Copyright 2020 VMware, Inc. // SPDX-License-Identifier: Apache-2.0 -package deploy +package kubeconfig import ( "fmt" @@ -10,13 +10,14 @@ import ( "sigs.k8s.io/yaml" ) -type KubeconfigRestricted struct { +// Restricted contains a kubernetes kubeconfig as a string +type Restricted struct { result string } // NewKubeconfigRestricted takes kubeconfig yaml as input and returns kubeconfig yaml with certain fields restricted (removed). // Developers may find it informative to view their own config at ~/.kube/config -func NewKubeconfigRestricted(input string) (*KubeconfigRestricted, error) { +func NewKubeconfigRestricted(input string) (*Restricted, error) { var inputConfig clientcmd.Config err := yaml.Unmarshal([]byte(input), &inputConfig) @@ -80,7 +81,8 @@ func NewKubeconfigRestricted(input string) (*KubeconfigRestricted, error) { return nil, fmt.Errorf("Marshaling kubeconfig: %s", err) } - return &KubeconfigRestricted{string(bs)}, nil + return &Restricted{string(bs)}, nil } -func (r *KubeconfigRestricted) AsYAML() string { return r.result } +// AsYAML returns a string formatted kubernetes kubeconfig +func (r *Restricted) AsYAML() string { return r.result } diff --git a/pkg/deploy/kubeconfig_secrets.go b/pkg/kubeconfig/kubeconfig_secrets.go similarity index 63% rename from pkg/deploy/kubeconfig_secrets.go rename to pkg/kubeconfig/kubeconfig_secrets.go index fc3cb78e5..d0fb2c1bf 100644 --- a/pkg/deploy/kubeconfig_secrets.go +++ b/pkg/kubeconfig/kubeconfig_secrets.go @@ -1,7 +1,7 @@ // Copyright 2020 VMware, Inc. // SPDX-License-Identifier: Apache-2.0 -package deploy +package kubeconfig import ( "context" @@ -13,37 +13,40 @@ import ( "k8s.io/client-go/kubernetes" ) -type KubeconfigSecrets struct { +// Secrets gets cluster access based on a secret +type Secrets struct { coreClient kubernetes.Interface } -func NewKubeconfigSecrets(coreClient kubernetes.Interface) *KubeconfigSecrets { - return &KubeconfigSecrets{coreClient} +// NewKubeconfigSecrets returns a Secrets +func NewKubeconfigSecrets(coreClient kubernetes.Interface) *Secrets { + return &Secrets{coreClient} } -func (s *KubeconfigSecrets) Find(genericOpts GenericOpts, - clusterOpts *v1alpha1.AppCluster) (ProcessedGenericOpts, error) { +// Find takes the location of the credentials secret and returns information to access the cluster +func (s *Secrets) Find(accessLocation AccessLocation, + clusterOpts *v1alpha1.AppCluster) (AccessInfo, error) { if clusterOpts == nil { - return ProcessedGenericOpts{}, fmt.Errorf("Internal inconsistency: Expected cluster to not be nil") + return AccessInfo{}, fmt.Errorf("Internal inconsistency: Expected cluster to not be nil") } if clusterOpts.KubeconfigSecretRef == nil { - return ProcessedGenericOpts{}, fmt.Errorf("Expected kubeconfig secret reference to be specified") + return AccessInfo{}, fmt.Errorf("Expected kubeconfig secret reference to be specified") } - kubeconfigYAML, err := s.fetchKubeconfigYAML(genericOpts.Namespace, clusterOpts.KubeconfigSecretRef) + kubeconfigYAML, err := s.fetchKubeconfigYAML(accessLocation.Namespace, clusterOpts.KubeconfigSecretRef) if err != nil { - return ProcessedGenericOpts{}, err + return AccessInfo{}, err } kubeconfigRestricted, err := NewKubeconfigRestricted(kubeconfigYAML) if err != nil { - return ProcessedGenericOpts{}, err + return AccessInfo{}, err } - pgoForCluster := ProcessedGenericOpts{ - Name: genericOpts.Name, + pgoForCluster := AccessInfo{ + Name: accessLocation.Name, // Override destination namespace; if it's empty // assume kubeconfig contains preferred namespace Namespace: clusterOpts.Namespace, @@ -53,7 +56,7 @@ func (s *KubeconfigSecrets) Find(genericOpts GenericOpts, return pgoForCluster, nil } -func (s *KubeconfigSecrets) fetchKubeconfigYAML(nsName string, +func (s *Secrets) fetchKubeconfigYAML(nsName string, secretRef *v1alpha1.AppClusterKubeconfigSecretRef) (string, error) { if len(nsName) == 0 { diff --git a/pkg/deploy/service_accounts.go b/pkg/kubeconfig/service_accounts.go similarity index 87% rename from pkg/deploy/service_accounts.go rename to pkg/kubeconfig/service_accounts.go index 0c720cd39..7b089e877 100644 --- a/pkg/deploy/service_accounts.go +++ b/pkg/kubeconfig/service_accounts.go @@ -1,7 +1,7 @@ // Copyright 2020 VMware, Inc. // SPDX-License-Identifier: Apache-2.0 -package deploy +package kubeconfig import ( "context" @@ -18,6 +18,8 @@ import ( "k8s.io/client-go/kubernetes" ) +// ServiceAccounts gets cluster access based on a serviceaccount +// It provides a tokenManager to cache service account tokens type ServiceAccounts struct { coreClient kubernetes.Interface log logr.Logger @@ -36,19 +38,20 @@ func NewServiceAccounts(coreClient kubernetes.Interface, log logr.Logger) *Servi tokenManager: tokenMgr, caCert: []byte(os.Getenv("KAPPCTRL_KUBERNETES_CA_DATA"))} } -func (s *ServiceAccounts) Find(genericOpts GenericOpts, saName string) (ProcessedGenericOpts, error) { - kubeconfigYAML, err := s.fetchServiceAccount(genericOpts.Namespace, saName) +// Find takes the location of the credentials service account and returns information to access the cluster +func (s *ServiceAccounts) Find(accessLocation AccessLocation, saName string) (AccessInfo, error) { + kubeconfigYAML, err := s.fetchServiceAccount(accessLocation.Namespace, saName) if err != nil { - return ProcessedGenericOpts{}, err + return AccessInfo{}, err } kubeconfigRestricted, err := NewKubeconfigRestricted(kubeconfigYAML) if err != nil { - return ProcessedGenericOpts{}, err + return AccessInfo{}, err } - pgoForSA := ProcessedGenericOpts{ - Name: genericOpts.Name, + pgoForSA := AccessInfo{ + Name: accessLocation.Name, Namespace: "", // Assume kubeconfig contains preferred namespace from SA Kubeconfig: kubeconfigRestricted, } diff --git a/pkg/packageinstall/packageinstall.go b/pkg/packageinstall/packageinstall.go index bb7edae85..1d0f9f176 100644 --- a/pkg/packageinstall/packageinstall.go +++ b/pkg/packageinstall/packageinstall.go @@ -16,7 +16,6 @@ import ( pkgclient "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apiserver/client/clientset/versioned" kcclient "github.com/vmware-tanzu/carvel-kapp-controller/pkg/client/clientset/versioned" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/client/clientset/versioned/scheme" - "github.com/vmware-tanzu/carvel-kapp-controller/pkg/deploy" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/reconciler" "github.com/vmware-tanzu/carvel-vendir/pkg/vendir/versions" verv1alpha1 "github.com/vmware-tanzu/carvel-vendir/pkg/vendir/versions/v1alpha1" @@ -30,6 +29,12 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" ) +// ComponentInfo provides information about components of the system required by a PKGI +type ComponentInfo interface { + KappControllerVersion() (semver.Version, error) + KubernetesVersion(serviceAccountName string, specCluster *v1alpha1.AppCluster, objMeta *metav1.ObjectMeta) (semver.Version, error) +} + const ( // DowngradableAnnKey specifies annotation that user can place on // PackageInstall to indicate that lower version of the package @@ -42,19 +47,18 @@ type PackageInstallCR struct { model *pkgingv1alpha1.PackageInstall unmodifiedModel *pkgingv1alpha1.PackageInstall - log logr.Logger - kcclient kcclient.Interface - pkgclient pkgclient.Interface - coreClient kubernetes.Interface - controllerVersion string - deployFactory deploy.Factory + log logr.Logger + kcclient kcclient.Interface + pkgclient pkgclient.Interface + coreClient kubernetes.Interface + compInfo ComponentInfo } func NewPackageInstallCR(model *pkgingv1alpha1.PackageInstall, log logr.Logger, - kcclient kcclient.Interface, pkgclient pkgclient.Interface, coreClient kubernetes.Interface, controllerVersion string, deployFactory deploy.Factory) *PackageInstallCR { + kcclient kcclient.Interface, pkgclient pkgclient.Interface, coreClient kubernetes.Interface, compInfo ComponentInfo) *PackageInstallCR { return &PackageInstallCR{model: model, unmodifiedModel: model.DeepCopy(), log: log, - kcclient: kcclient, pkgclient: pkgclient, coreClient: coreClient, controllerVersion: controllerVersion, deployFactory: deployFactory} + kcclient: kcclient, pkgclient: pkgclient, coreClient: coreClient, compInfo: compInfo} } func (pi *PackageInstallCR) Reconcile() (reconcile.Result, error) { @@ -225,17 +229,6 @@ func (pi *PackageInstallCR) clusterVersionConstraintsSatisfied(pkg *datapkgingv1 return constraintsFunc(clusterVersion) } -func dropVersionPreReleaseAndBuild(versionStr string) (semver.Version, error) { - v, err := semver.ParseTolerant(versionStr) - if err != nil { - return v, err - } - v.Pre = semver.PRVersion{} - v.Build = semver.BuildMeta{} - - return v, nil -} - func (pi *PackageInstallCR) kcVersionConstraintsSatisfied(pkg *datapkgingv1alpha1.Package) bool { if pkg.Spec.KappControllerVersionSelection == nil || pkg.Spec.KappControllerVersionSelection.Constraints == "" { return true @@ -248,12 +241,14 @@ func (pi *PackageInstallCR) kcVersionConstraintsSatisfied(pkg *datapkgingv1alpha return true } - v, err := dropVersionPreReleaseAndBuild(pi.controllerVersion) + v, err := pi.compInfo.KappControllerVersion() if err != nil { - pi.log.Error(err, "Unable to parse kapp-controller version", "version string", pi.controllerVersion) return false } + v.Pre = semver.PRVersion{} + v.Build = semver.BuildMeta{} + constraints, _ := semver.ParseRange(pkg.Spec.KappControllerVersionSelection.Constraints) // ignore err because validation should have already caught it return constraints(v) } @@ -305,19 +300,13 @@ func (pi *PackageInstallCR) referencedPkgVersion() (datapkgingv1alpha1.Package, vcc := []versions.ConstraintCallback{} // we only need to populate the versionInfo we know that the packages have constraints that will require this info. - v := semver.Version{} if requiresClusterVersion { - vi, err := pi.deployFactory.GetClusterVersion(pi.model.Spec.ServiceAccountName, pi.model.Spec.Cluster, &pi.model.ObjectMeta, pi.log) - if err != nil { - pi.log.Error(err, "Unable to retrieve cluster kubernetes version") - return datapkgingv1alpha1.Package{}, err - } - - v, err = dropVersionPreReleaseAndBuild(vi.GitVersion) + v, err := pi.compInfo.KubernetesVersion(pi.model.Spec.ServiceAccountName, pi.model.Spec.Cluster, &pi.model.ObjectMeta) if err != nil { - pi.log.Error(err, "Unable to parse cluster kubernetes version", "version string", vi.GitVersion) - return datapkgingv1alpha1.Package{}, err + return datapkgingv1alpha1.Package{}, fmt.Errorf("Unable to get kubernetes version: %s", err) } + v.Pre = semver.PRVersion{} + v.Build = semver.BuildMeta{} k8sConstraint := func(pkgVer string) bool { pkg := versionToPkg[pkgVer] diff --git a/pkg/packageinstall/packageinstall_deletion_test.go b/pkg/packageinstall/packageinstall_deletion_test.go index 6a4aa777d..916b7b043 100644 --- a/pkg/packageinstall/packageinstall_deletion_test.go +++ b/pkg/packageinstall/packageinstall_deletion_test.go @@ -6,13 +6,12 @@ package packageinstall import ( "testing" + "github.com/k14s/semver/v4" "github.com/stretchr/testify/assert" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1" pkgingv1alpha1 "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/packaging/v1alpha1" fakeapiserver "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apiserver/client/clientset/versioned/fake" fakekappctrl "github.com/vmware-tanzu/carvel-kapp-controller/pkg/client/clientset/versioned/fake" - "github.com/vmware-tanzu/carvel-kapp-controller/pkg/deploy" - "github.com/vmware-tanzu/carvel-kapp-controller/pkg/exec" versions "github.com/vmware-tanzu/carvel-vendir/pkg/vendir/versions/v1alpha1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" @@ -65,9 +64,8 @@ func Test_PackageInstallDeletion(t *testing.T) { pkgClient := fakeapiserver.NewSimpleClientset() appClient := fakekappctrl.NewSimpleClientset(pkgInstall, existingApp) coreClient := fake.NewSimpleClientset() - deployFac := deploy.NewFactory(coreClient, nil, exec.NewPlainCmdRunner(), log) - ip := NewPackageInstallCR(pkgInstall, log, appClient, pkgClient, coreClient, "0.42.31337", deployFac) + ip := NewPackageInstallCR(pkgInstall, log, appClient, pkgClient, coreClient, FakeComponentInfo{KCVersion: semver.MustParse("0.42.31337")}) _, err := ip.Reconcile() assert.Nil(t, err) @@ -79,3 +77,16 @@ func Test_PackageInstallDeletion(t *testing.T) { assert.Equal(t, true, app.Spec.Canceled) }) } + +type FakeComponentInfo struct { + KCVersion semver.Version + K8sVersion semver.Version +} + +func (f FakeComponentInfo) KappControllerVersion() (semver.Version, error) { + return f.KCVersion, nil +} + +func (f FakeComponentInfo) KubernetesVersion(serviceAccountName string, specCluster *v1alpha1.AppCluster, objMeta *metav1.ObjectMeta) (semver.Version, error) { + return f.K8sVersion, nil +} diff --git a/pkg/packageinstall/packageinstall_downgrade_test.go b/pkg/packageinstall/packageinstall_downgrade_test.go index 0caa531bb..bc456ff28 100644 --- a/pkg/packageinstall/packageinstall_downgrade_test.go +++ b/pkg/packageinstall/packageinstall_downgrade_test.go @@ -6,6 +6,7 @@ package packageinstall import ( "testing" + "github.com/k14s/semver/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1" @@ -13,8 +14,6 @@ import ( datapkgingv1alpha1 "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apiserver/apis/datapackaging/v1alpha1" fakeapiserver "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apiserver/client/clientset/versioned/fake" fakekappctrl "github.com/vmware-tanzu/carvel-kapp-controller/pkg/client/clientset/versioned/fake" - "github.com/vmware-tanzu/carvel-kapp-controller/pkg/deploy" - "github.com/vmware-tanzu/carvel-kapp-controller/pkg/exec" versions "github.com/vmware-tanzu/carvel-vendir/pkg/vendir/versions/v1alpha1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" @@ -92,7 +91,6 @@ func Test_PackageInstallVersionDowngrades(t *testing.T) { pkgClient := fakeapiserver.NewSimpleClientset(&pkg1, &pkg2) appClient := fakekappctrl.NewSimpleClientset(pkgInstall, existingApp) fakek8s := fake.NewSimpleClientset() - deployFac := deploy.NewFactory(fakek8s, nil, exec.NewPlainCmdRunner(), log) // mock the kubernetes server version fakeDiscovery, _ := fakek8s.Discovery().(*fakediscovery.FakeDiscovery) @@ -100,7 +98,7 @@ func Test_PackageInstallVersionDowngrades(t *testing.T) { GitVersion: "v0.20.0", } - ip := NewPackageInstallCR(pkgInstall, log, appClient, pkgClient, fakek8s, "0.42.31337", deployFac) + ip := NewPackageInstallCR(pkgInstall, log, appClient, pkgClient, fakek8s, FakeComponentInfo{KCVersion: semver.MustParse("0.42.31337")}) _, err := ip.Reconcile() assert.Nil(t, err) @@ -142,7 +140,6 @@ func Test_PackageInstallVersionDowngrades(t *testing.T) { pkgClient := fakeapiserver.NewSimpleClientset(&pkg1, &pkg2) appClient := fakekappctrl.NewSimpleClientset(pkgInstall, existingApp) fakek8s := fake.NewSimpleClientset() - deployFac := deploy.NewFactory(fakek8s, nil, exec.NewPlainCmdRunner(), log) // mock the kubernetes server version fakeDiscovery, _ := fakek8s.Discovery().(*fakediscovery.FakeDiscovery) @@ -150,7 +147,7 @@ func Test_PackageInstallVersionDowngrades(t *testing.T) { GitVersion: "v0.20.0", } - ip := NewPackageInstallCR(pkgInstall, log, appClient, pkgClient, fakek8s, "0.42.31337", deployFac) + ip := NewPackageInstallCR(pkgInstall, log, appClient, pkgClient, fakek8s, FakeComponentInfo{KCVersion: semver.MustParse("0.42.31337")}) _, err := ip.Reconcile() assert.Nil(t, err) @@ -192,7 +189,6 @@ func Test_PackageInstallVersionDowngrades(t *testing.T) { pkgClient := fakeapiserver.NewSimpleClientset(&pkg1, &pkg2) appClient := fakekappctrl.NewSimpleClientset(pkgInstall, existingApp) fakek8s := fake.NewSimpleClientset() - deployFac := deploy.NewFactory(fakek8s, nil, exec.NewPlainCmdRunner(), log) // mock the kubernetes server version fakeDiscovery, _ := fakek8s.Discovery().(*fakediscovery.FakeDiscovery) @@ -200,7 +196,7 @@ func Test_PackageInstallVersionDowngrades(t *testing.T) { GitVersion: "v0.20.0", } - ip := NewPackageInstallCR(pkgInstall, log, appClient, pkgClient, fakek8s, "0.42.31337", deployFac) + ip := NewPackageInstallCR(pkgInstall, log, appClient, pkgClient, fakek8s, FakeComponentInfo{KCVersion: semver.MustParse("0.42.31337")}) _, err := ip.Reconcile() assert.Nil(t, err) @@ -248,7 +244,6 @@ func Test_PackageInstallVersionDowngrades(t *testing.T) { pkgClient := fakeapiserver.NewSimpleClientset(&pkg1, &pkg2) appClient := fakekappctrl.NewSimpleClientset(pkgInstall, existingApp) fakek8s := fake.NewSimpleClientset() - deployFac := deploy.NewFactory(fakek8s, nil, exec.NewPlainCmdRunner(), log) // mock the kubernetes server version fakeDiscovery, _ := fakek8s.Discovery().(*fakediscovery.FakeDiscovery) @@ -256,7 +251,7 @@ func Test_PackageInstallVersionDowngrades(t *testing.T) { GitVersion: "v0.20.0", } - ip := NewPackageInstallCR(pkgInstall, log, appClient, pkgClient, fakek8s, "0.42.31337", deployFac) + ip := NewPackageInstallCR(pkgInstall, log, appClient, pkgClient, fakek8s, FakeComponentInfo{KCVersion: semver.MustParse("0.42.31337")}) _, err := ip.Reconcile() assert.Nil(t, err) diff --git a/pkg/packageinstall/packageinstall_test.go b/pkg/packageinstall/packageinstall_test.go index 22055b48a..3a2c2a871 100644 --- a/pkg/packageinstall/packageinstall_test.go +++ b/pkg/packageinstall/packageinstall_test.go @@ -8,14 +8,12 @@ import ( "reflect" "testing" + "github.com/k14s/semver/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1" pkgingv1alpha1 "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/packaging/v1alpha1" datapkgingv1alpha1 "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apiserver/apis/datapackaging/v1alpha1" - "github.com/vmware-tanzu/carvel-kapp-controller/pkg/deploy" - "github.com/vmware-tanzu/carvel-kapp-controller/pkg/exec" - fakeapiserver "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apiserver/client/clientset/versioned/fake" fakekappctrl "github.com/vmware-tanzu/carvel-kapp-controller/pkg/client/clientset/versioned/fake" versions "github.com/vmware-tanzu/carvel-vendir/pkg/vendir/versions/v1alpha1" @@ -32,7 +30,6 @@ import ( // https://github.com/vmware-tanzu/carvel-kapp-controller/issues/116 func Test_PackageRefWithPrerelease_IsFound(t *testing.T) { log := logf.Log.WithName("kc") - fakek8s := fake.NewSimpleClientset() // PackageMetadata with prerelease version expectedPackageVersion := datapkgingv1alpha1.Package{ @@ -48,14 +45,6 @@ func Test_PackageRefWithPrerelease_IsFound(t *testing.T) { // Load package into fake client fakePkgClient := fakeapiserver.NewSimpleClientset(&expectedPackageVersion) - // mock the kubernetes server version - fakeDiscovery, _ := fakek8s.Discovery().(*fakediscovery.FakeDiscovery) - fakeDiscovery.FakedServerVersion = &version.Info{ - GitVersion: "v0.20.0", - } - - deployFactory := deploy.NewFactory(fakek8s, nil, exec.NewPlainCmdRunner(), log) - // PackageInstall that has PackageRef with prerelease ip := PackageInstallCR{ model: &pkgingv1alpha1.PackageInstall{ @@ -75,9 +64,9 @@ func Test_PackageRefWithPrerelease_IsFound(t *testing.T) { ServiceAccountName: "use-local-cluster-sa", // saname being present indicates use local cluster version }, }, - pkgclient: fakePkgClient, - deployFactory: deployFactory, - log: log, + pkgclient: fakePkgClient, + log: log, + compInfo: FakeComponentInfo{K8sVersion: semver.MustParse("0.20.0")}, } out, err := ip.referencedPkgVersion() @@ -100,13 +89,6 @@ func Test_PackageWithConstraints(t *testing.T) { fakek8s := fake.NewSimpleClientset() pkg := generatePackageWithConstraints("pkg.test.carvel.dev", "0.0.0", ">1.0.0 <2.0.0", ">0.15.0") fakePkgClient := fakeapiserver.NewSimpleClientset(&pkg) - deployFactory := deploy.NewFactory(fakek8s, nil, exec.NewPlainCmdRunner(), log) - - // mock the kubernetes server version - fakeDiscovery, _ := fakek8s.Discovery().(*fakediscovery.FakeDiscovery) - fakeDiscovery.FakedServerVersion = &version.Info{ - GitVersion: "v0.20.0", - } ip := PackageInstallCR{ model: &pkgingv1alpha1.PackageInstall{ @@ -123,11 +105,10 @@ func Test_PackageWithConstraints(t *testing.T) { ServiceAccountName: "use-local-cluster-sa", // saname being present indicates use local cluster version }, }, - pkgclient: fakePkgClient, - controllerVersion: "1.5.0", - log: log, - coreClient: fakek8s, - deployFactory: deployFactory, + pkgclient: fakePkgClient, + log: log, + coreClient: fakek8s, + compInfo: FakeComponentInfo{KCVersion: semver.MustParse("1.5.0"), K8sVersion: semver.MustParse("0.20.0")}, } // all constraints met @@ -135,7 +116,7 @@ func Test_PackageWithConstraints(t *testing.T) { require.NoError(t, err) // kapp-controller version constraint fail - ip.controllerVersion = "3.0.0" + ip.compInfo = FakeComponentInfo{KCVersion: semver.MustParse("3.0.0"), K8sVersion: semver.MustParse("0.20.0")} _, err = ip.referencedPkgVersion() require.Error(t, err) assert.ErrorContains(t, err, "after-kubernetes-version-check=1") @@ -149,9 +130,7 @@ func Test_PackageWithConstraints(t *testing.T) { require.NoError(t, err) // kubernetes version constraint fail - fakeDiscovery.FakedServerVersion = &version.Info{ - GitVersion: "v0.0.0", - } + ip.compInfo = FakeComponentInfo{KCVersion: semver.MustParse("1.5.0"), K8sVersion: semver.MustParse("0.0.0")} _, err = ip.referencedPkgVersion() require.Error(t, err) assert.ErrorContains(t, err, "after-kubernetes-version-check=0") @@ -185,10 +164,10 @@ func Test_Package_NotFound(t *testing.T) { }, }, }, - pkgclient: fakePkgClient, - controllerVersion: "0.42.0", - log: log, - coreClient: fakek8s, + pkgclient: fakePkgClient, + compInfo: FakeComponentInfo{KCVersion: semver.MustParse("0.42.0")}, + log: log, + coreClient: fakek8s, } _, err := ip.referencedPkgVersion() @@ -201,7 +180,6 @@ func Test_Package_ConstraintNotGiven_ErrorDoesNotContainMessage(t *testing.T) { fakek8s := fake.NewSimpleClientset() pkg := generatePackageWithConstraints("pkg.test.carvel.dev", "0.0.0", "1.0.0", "") fakePkgClient := fakeapiserver.NewSimpleClientset(&pkg) - deployFactory := deploy.NewFactory(fakek8s, nil, exec.NewPlainCmdRunner(), log) ip := PackageInstallCR{ model: &pkgingv1alpha1.PackageInstall{ @@ -218,11 +196,10 @@ func Test_Package_ConstraintNotGiven_ErrorDoesNotContainMessage(t *testing.T) { ServiceAccountName: "use-local-cluster-sa", // saname being present indicates use local cluster version }, }, - pkgclient: fakePkgClient, - controllerVersion: "1.5.0", - log: log, - coreClient: fakek8s, - deployFactory: deployFactory, + pkgclient: fakePkgClient, + compInfo: FakeComponentInfo{KCVersion: semver.MustParse("1.5.0")}, + log: log, + coreClient: fakek8s, } _, err := ip.referencedPkgVersion() @@ -237,13 +214,6 @@ func Test_PackageWithConstraintsWithPrerelease(t *testing.T) { pkg := generatePackageWithConstraints("pkg.test.carvel.dev", "0.0.0", ">1.0.0 <2.0.0", "0.10.0") pkg2 := generatePackageWithConstraints("pkg.test.carvel.dev", "2.0.0", ">1.0.0 <2.0.0", "0.20.0") fakePkgClient := fakeapiserver.NewSimpleClientset(&pkg, &pkg2) - deployFactory := deploy.NewFactory(fakek8s, nil, exec.NewPlainCmdRunner(), log) - - // mock the kubernetes server version - fakeDiscovery, _ := fakek8s.Discovery().(*fakediscovery.FakeDiscovery) - fakeDiscovery.FakedServerVersion = &version.Info{ - GitVersion: "v0.20.0-gke.100", - } ip := PackageInstallCR{ model: &pkgingv1alpha1.PackageInstall{ @@ -260,11 +230,10 @@ func Test_PackageWithConstraintsWithPrerelease(t *testing.T) { ServiceAccountName: "use-local-cluster-sa", // saname being present indicates use local cluster version }, }, - pkgclient: fakePkgClient, - controllerVersion: "1.5.0", - log: log, - coreClient: fakek8s, - deployFactory: deployFactory, + pkgclient: fakePkgClient, + compInfo: FakeComponentInfo{KCVersion: semver.MustParse("1.5.0"), K8sVersion: semver.MustParse("0.20.0-gke.100")}, + log: log, + coreClient: fakek8s, } out, err := ip.referencedPkgVersion() @@ -280,13 +249,6 @@ func Test_PackageWithConstraints_HighestMatch(t *testing.T) { pkg2 := generatePackageWithConstraints(pkgName, "0.5.0", ">0.1.0", ">0.1.0") // this one is the highest installable version pkg3 := generatePackageWithConstraints(pkgName, "1.4.1", ">2.0.0", "") // higher version uninstallable fakePkgClient := fakeapiserver.NewSimpleClientset(&pkg1, &pkg2, &pkg3) - deployFactory := deploy.NewFactory(fakek8s, nil, exec.NewPlainCmdRunner(), log) - - // mock the kubernetes server version - fakeDiscovery, _ := fakek8s.Discovery().(*fakediscovery.FakeDiscovery) - fakeDiscovery.FakedServerVersion = &version.Info{ - GitVersion: "v0.20.0", - } ip := PackageInstallCR{ model: &pkgingv1alpha1.PackageInstall{ @@ -303,11 +265,10 @@ func Test_PackageWithConstraints_HighestMatch(t *testing.T) { ServiceAccountName: "use-local-cluster-sa", // saname being present indicates use local cluster version }, }, - pkgclient: fakePkgClient, - controllerVersion: "1.5.0", - log: log, - coreClient: fakek8s, - deployFactory: deployFactory, + pkgclient: fakePkgClient, + compInfo: FakeComponentInfo{KCVersion: semver.MustParse("1.5.0"), K8sVersion: semver.MustParse("0.20.0")}, + log: log, + coreClient: fakek8s, } out, err := ip.referencedPkgVersion() @@ -329,7 +290,6 @@ func Test_PackageRefWithPrerelease_DoesNotRequirePrereleaseMarker(t *testing.T) log := logf.Log.WithName("kc") fakek8s := fake.NewSimpleClientset() fakePkgClient := fakeapiserver.NewSimpleClientset(&expectedPackageVersion) - deployFactory := deploy.NewFactory(fakek8s, nil, exec.NewPlainCmdRunner(), log) // mock the kubernetes server version fakeDiscovery, _ := fakek8s.Discovery().(*fakediscovery.FakeDiscovery) @@ -352,9 +312,9 @@ func Test_PackageRefWithPrerelease_DoesNotRequirePrereleaseMarker(t *testing.T) ServiceAccountName: "use-local-cluster-sa", // saname being present indicates use local cluster version }, }, - pkgclient: fakePkgClient, - deployFactory: deployFactory, - log: log, + pkgclient: fakePkgClient, + compInfo: FakeComponentInfo{KCVersion: semver.MustParse("1.5.0")}, + log: log, } out, err := ip.referencedPkgVersion() @@ -388,7 +348,6 @@ func Test_PackageRefUsesName(t *testing.T) { fakePkgClient := fakeapiserver.NewSimpleClientset(&expectedPackageVersion, &alternatePackageVersion) log := logf.Log.WithName("kc") fakek8s := fake.NewSimpleClientset() - deployFactory := deploy.NewFactory(fakek8s, nil, exec.NewPlainCmdRunner(), log) // mock the kubernetes server version fakeDiscovery, _ := fakek8s.Discovery().(*fakediscovery.FakeDiscovery) @@ -412,9 +371,8 @@ func Test_PackageRefUsesName(t *testing.T) { ServiceAccountName: "use-local-cluster-sa", // saname being present indicates use local cluster version }, }, - pkgclient: fakePkgClient, - log: log, - deployFactory: deployFactory, + pkgclient: fakePkgClient, + log: log, } out, err := ip.referencedPkgVersion() @@ -470,7 +428,6 @@ func Test_PlaceHolderSecretCreated_WhenPackageHasNoSecretRef(t *testing.T) { log := logf.Log.WithName("kc") fakekctrl := fakekappctrl.NewSimpleClientset(model) fakek8s := fake.NewSimpleClientset() - deployFac := deploy.NewFactory(fakek8s, nil, exec.NewPlainCmdRunner(), log) // mock the kubernetes server version fakeDiscovery, _ := fakek8s.Discovery().(*fakediscovery.FakeDiscovery) @@ -478,7 +435,7 @@ func Test_PlaceHolderSecretCreated_WhenPackageHasNoSecretRef(t *testing.T) { GitVersion: "v0.20.0", } - ip := NewPackageInstallCR(model, log, fakekctrl, fakePkgClient, fakek8s, "0.42.31337", deployFac) + ip := NewPackageInstallCR(model, log, fakekctrl, fakePkgClient, fakek8s, FakeComponentInfo{KCVersion: semver.MustParse("0.42.31337")}) _, err := ip.Reconcile() assert.Nil(t, err) @@ -551,14 +508,13 @@ func Test_PlaceHolderSecretsCreated_WhenPackageHasMultipleFetchStages(t *testing log := logf.Log.WithName("kc") fakekctrl := fakekappctrl.NewSimpleClientset(model) fakek8s := fake.NewSimpleClientset() - deployFac := deploy.NewFactory(fakek8s, nil, exec.NewPlainCmdRunner(), log) // mock the kubernetes server version fakeDiscovery, _ := fakek8s.Discovery().(*fakediscovery.FakeDiscovery) fakeDiscovery.FakedServerVersion = &version.Info{ GitVersion: "v0.20.0", } - ip := NewPackageInstallCR(model, log, fakekctrl, fakePkgClient, fakek8s, "0.42.31337", deployFac) + ip := NewPackageInstallCR(model, log, fakekctrl, fakePkgClient, fakek8s, FakeComponentInfo{KCVersion: semver.MustParse("0.42.31337")}) _, err := ip.Reconcile() assert.Nil(t, err) @@ -641,7 +597,6 @@ func Test_PlaceHolderSecretsNotCreated_WhenFetchStagesHaveSecrets(t *testing.T) log := logf.Log.WithName("kc") fakekctrl := fakekappctrl.NewSimpleClientset(model) fakek8s := fake.NewSimpleClientset() - deployFac := deploy.NewFactory(fakek8s, nil, exec.NewPlainCmdRunner(), log) // mock the kubernetes server version fakeDiscovery, _ := fakek8s.Discovery().(*fakediscovery.FakeDiscovery) @@ -649,7 +604,7 @@ func Test_PlaceHolderSecretsNotCreated_WhenFetchStagesHaveSecrets(t *testing.T) GitVersion: "v0.20.0", } - ip := NewPackageInstallCR(model, log, fakekctrl, fakePkgClient, fakek8s, "0.42.31337", deployFac) + ip := NewPackageInstallCR(model, log, fakekctrl, fakePkgClient, fakek8s, FakeComponentInfo{KCVersion: semver.MustParse("0.42.31337")}) _, err := ip.Reconcile() assert.Nil(t, err) @@ -726,8 +681,7 @@ func Test_PlaceHolderSecretCreated_WhenPackageInstallUpdated(t *testing.T) { fakekctrl := fakekappctrl.NewSimpleClientset(model, existingApp) fakek8s := fake.NewSimpleClientset() - deployFac := deploy.NewFactory(fakek8s, nil, exec.NewPlainCmdRunner(), log) - ip := NewPackageInstallCR(model, log, fakekctrl, fakePkgClient, fakek8s, "0.42.31337", deployFac) + ip := NewPackageInstallCR(model, log, fakekctrl, fakePkgClient, fakek8s, FakeComponentInfo{KCVersion: semver.MustParse("0.42.31337")}) // mock the kubernetes server version fakeDiscovery, _ := fakek8s.Discovery().(*fakediscovery.FakeDiscovery) diff --git a/pkg/packageinstall/reconciler.go b/pkg/packageinstall/reconciler.go index e713c3206..367d0270f 100644 --- a/pkg/packageinstall/reconciler.go +++ b/pkg/packageinstall/reconciler.go @@ -13,7 +13,6 @@ import ( datapkgingv1alpha1 "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apiserver/apis/datapackaging/v1alpha1" pkgclient "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apiserver/client/clientset/versioned" kcclient "github.com/vmware-tanzu/carvel-kapp-controller/pkg/client/clientset/versioned" - "github.com/vmware-tanzu/carvel-kapp-controller/pkg/deploy" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -25,26 +24,24 @@ import ( // Reconciler is responsible for reconciling PackageInstalls. type Reconciler struct { - deployFactory deploy.Factory kcClient kcclient.Interface pkgClient pkgclient.Interface coreClient kubernetes.Interface pkgToPkgInstallHandler *PackageInstallVersionHandler - controllerVersion string + compInfo ComponentInfo log logr.Logger } // NewReconciler is the constructor for the Reconciler struct -func NewReconciler(deployFactory deploy.Factory, kcClient kcclient.Interface, pkgClient pkgclient.Interface, +func NewReconciler(kcClient kcclient.Interface, pkgClient pkgclient.Interface, coreClient kubernetes.Interface, pkgToPkgInstallHandler *PackageInstallVersionHandler, - log logr.Logger, controllerVersion string) *Reconciler { + log logr.Logger, compInfo ComponentInfo) *Reconciler { - return &Reconciler{deployFactory: deployFactory, - kcClient: kcClient, + return &Reconciler{kcClient: kcClient, pkgClient: pkgClient, coreClient: coreClient, pkgToPkgInstallHandler: pkgToPkgInstallHandler, - controllerVersion: controllerVersion, + compInfo: compInfo, log: log} } @@ -88,5 +85,5 @@ func (r *Reconciler) Reconcile(ctx context.Context, request reconcile.Request) ( return reconcile.Result{}, err } - return NewPackageInstallCR(existingPkgInstall, log, r.kcClient, r.pkgClient, r.coreClient, r.controllerVersion, r.deployFactory).Reconcile() + return NewPackageInstallCR(existingPkgInstall, log, r.kcClient, r.pkgClient, r.coreClient, r.compInfo).Reconcile() } diff --git a/pkg/pkgrepository/app_deploy.go b/pkg/pkgrepository/app_deploy.go index 10d5a4f36..b4e29e228 100644 --- a/pkg/pkgrepository/app_deploy.go +++ b/pkg/pkgrepository/app_deploy.go @@ -9,6 +9,7 @@ import ( "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1" ctldep "github.com/vmware-tanzu/carvel-kapp-controller/pkg/deploy" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/exec" + "github.com/vmware-tanzu/carvel-kapp-controller/pkg/kubeconfig" ) func (a *App) deploy(tplOutput string) exec.CmdRunResult { @@ -71,6 +72,6 @@ func (a *App) delete() exec.CmdRunResult { } func (a *App) newKapp(kapp v1alpha1.AppDeployKapp, cancelCh chan struct{}) (*ctldep.Kapp, error) { - genericOpts := ctldep.GenericOpts{Name: a.app.Name, Namespace: a.app.Namespace} + genericOpts := kubeconfig.AccessInfo{Name: a.app.Name, Namespace: a.app.Namespace} return a.deployFactory.NewKappPrivilegedForPackageRepository(kapp, genericOpts, cancelCh) } diff --git a/pkg/pkgrepository/app_factory.go b/pkg/pkgrepository/app_factory.go index 022d6d82d..8d6078f0d 100644 --- a/pkg/pkgrepository/app_factory.go +++ b/pkg/pkgrepository/app_factory.go @@ -12,6 +12,7 @@ import ( "github.com/vmware-tanzu/carvel-kapp-controller/pkg/deploy" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/exec" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/fetch" + "github.com/vmware-tanzu/carvel-kapp-controller/pkg/kubeconfig" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/template" "k8s.io/client-go/kubernetes" ) @@ -22,12 +23,13 @@ type AppFactory struct { AppClient kcclient.Interface KcConfig *config.Config CmdRunner exec.CmdRunner + Kubeconf *kubeconfig.Kubeconfig } // NewCRDPackageRepo constructs "hidden" App to reconcile PackageRepository. func (f *AppFactory) NewCRDPackageRepo(app *kcv1alpha1.App, pkgr *pkgv1alpha1.PackageRepository, log logr.Logger) *CRDApp { fetchFactory := fetch.NewFactory(f.CoreClient, fetch.VendirOpts{SkipTLSConfig: f.KcConfig}, f.CmdRunner) templateFactory := template.NewFactory(f.CoreClient, fetchFactory, false, f.CmdRunner) - deployFactory := deploy.NewFactory(f.CoreClient, nil, f.CmdRunner, log) + deployFactory := deploy.NewFactory(f.CoreClient, f.Kubeconf, nil, f.CmdRunner, log) return NewCRDApp(app, pkgr, log, f.AppClient, fetchFactory, templateFactory, deployFactory) } diff --git a/pkg/pkgrepository/app_reconcile_test.go b/pkg/pkgrepository/app_reconcile_test.go index de8bcf773..42276f563 100644 --- a/pkg/pkgrepository/app_reconcile_test.go +++ b/pkg/pkgrepository/app_reconcile_test.go @@ -13,6 +13,7 @@ import ( "github.com/vmware-tanzu/carvel-kapp-controller/pkg/deploy" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/exec" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/fetch" + "github.com/vmware-tanzu/carvel-kapp-controller/pkg/kubeconfig" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/template" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -40,7 +41,7 @@ func Test_NoInspectReconcile_IfNoDeployAttempted(t *testing.T) { kappcs := fake.NewSimpleClientset() fetchFac := fetch.NewFactory(k8scs, fetch.VendirOpts{}, exec.NewPlainCmdRunner()) tmpFac := template.NewFactory(k8scs, fetchFac, false, exec.NewPlainCmdRunner()) - deployFac := deploy.NewFactory(k8scs, nil, exec.NewPlainCmdRunner(), log) + deployFac := deploy.NewFactory(k8scs, kubeconfig.NewKubeconfig(k8scs, log), nil, exec.NewPlainCmdRunner(), log) pkgr := v1alpha12.PackageRepository{} crdApp := NewCRDApp(&app, &pkgr, log, kappcs, fetchFac, tmpFac, deployFac) @@ -95,7 +96,7 @@ func Test_TemplateError_DisplayedInStatus_UsefulErrorMessageProperty(t *testing. kappcs := fake.NewSimpleClientset() fetchFac := fetch.NewFactory(k8scs, fetch.VendirOpts{}, exec.NewPlainCmdRunner()) tmpFac := template.NewFactory(k8scs, fetchFac, false, exec.NewPlainCmdRunner()) - deployFac := deploy.NewFactory(k8scs, nil, exec.NewPlainCmdRunner(), log) + deployFac := deploy.NewFactory(k8scs, kubeconfig.NewKubeconfig(k8scs, log), nil, exec.NewPlainCmdRunner(), log) pkgr := v1alpha12.PackageRepository{} crdApp := NewCRDApp(&app, &pkgr, log, kappcs, fetchFac, tmpFac, deployFac) @@ -150,7 +151,7 @@ func TestInvalidPackageRepositoryFormat(t *testing.T) { kappcs := fake.NewSimpleClientset() fetchFac := fetch.NewFactory(k8scs, fetch.VendirOpts{}, exec.NewPlainCmdRunner()) tmpFac := template.NewFactory(k8scs, fetchFac, false, exec.NewPlainCmdRunner()) - deployFac := deploy.NewFactory(k8scs, nil, exec.NewPlainCmdRunner(), log) + deployFac := deploy.NewFactory(k8scs, kubeconfig.NewKubeconfig(k8scs, log), nil, exec.NewPlainCmdRunner(), log) pkgr := v1alpha12.PackageRepository{} crdApp := NewCRDApp(&app, &pkgr, log, kappcs, fetchFac, tmpFac, deployFac) diff --git a/pkg/pkgrepository/app_template.go b/pkg/pkgrepository/app_template.go index c4e1ed563..3e3f04dac 100644 --- a/pkg/pkgrepository/app_template.go +++ b/pkg/pkgrepository/app_template.go @@ -49,7 +49,9 @@ func (a *App) template(dirPath string) exec.CmdRunResult { IgnoreUnknownComments: true, Paths: []string{"packages"}, } - result, _ := a.templateFactory.NewYtt(template1, appContext).TemplateDir(dirPath) + additionalValues := ctltpl.AdditionalDownwardAPIValues{} + + result, _ := a.templateFactory.NewYtt(template1, appContext, additionalValues).TemplateDir(dirPath) if result.Error != nil { return result } @@ -58,7 +60,7 @@ func (a *App) template(dirPath string) exec.CmdRunResult { // some of which could be migrated to go code stream := strings.NewReader(result.Stdout) result = a.templateFactory.NewYtt( - a.yttTemplateCleanRs(), appContext).TemplateStream(stream, dirPath) + a.yttTemplateCleanRs(), appContext, additionalValues).TemplateStream(stream, dirPath) if result.Error != nil { return result } @@ -75,7 +77,7 @@ func (a *App) template(dirPath string) exec.CmdRunResult { // on identical resources provided by multiple pkgrs stream = strings.NewReader(resources) result = a.templateFactory.NewYtt( - a.yttTemplateAddIdenticalRsRebase(), appContext).TemplateStream(stream, dirPath) + a.yttTemplateAddIdenticalRsRebase(), appContext, additionalValues).TemplateStream(stream, dirPath) if result.Error != nil { return result } diff --git a/pkg/pkgrepository/app_test.go b/pkg/pkgrepository/app_test.go index 9dba69adc..a2df407e1 100644 --- a/pkg/pkgrepository/app_test.go +++ b/pkg/pkgrepository/app_test.go @@ -12,6 +12,7 @@ import ( "github.com/vmware-tanzu/carvel-kapp-controller/pkg/deploy" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/exec" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/fetch" + "github.com/vmware-tanzu/carvel-kapp-controller/pkg/kubeconfig" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/reftracker" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/template" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -48,9 +49,9 @@ func Test_SecretRefs_RetrievesAllSecretRefs(t *testing.T) { k8scs := k8sfake.NewSimpleClientset() fetchFac := fetch.NewFactory(k8scs, fetch.VendirOpts{}, exec.NewPlainCmdRunner()) tmpFac := template.NewFactory(k8scs, fetchFac, false, exec.NewPlainCmdRunner()) - deployFac := deploy.NewFactory(k8scs, nil, exec.NewPlainCmdRunner(), log) + deployFac := deploy.NewFactory(k8scs, kubeconfig.NewKubeconfig(k8scs, log), nil, exec.NewPlainCmdRunner(), log) - app := apppkg.NewApp(appWithRefs, apppkg.Hooks{}, fetchFac, tmpFac, deployFac, log, nil) + app := apppkg.NewApp(appWithRefs, apppkg.Hooks{}, fetchFac, tmpFac, deployFac, log, nil, nil) out := app.SecretRefs() if !reflect.DeepEqual(out, expected) { @@ -74,9 +75,9 @@ func Test_SecretRefs_RetrievesNoSecretRefs_WhenNonePresent(t *testing.T) { k8scs := k8sfake.NewSimpleClientset() fetchFac := fetch.NewFactory(k8scs, fetch.VendirOpts{}, exec.NewPlainCmdRunner()) tmpFac := template.NewFactory(k8scs, fetchFac, false, exec.NewPlainCmdRunner()) - deployFac := deploy.NewFactory(k8scs, nil, exec.NewPlainCmdRunner(), log) + deployFac := deploy.NewFactory(k8scs, kubeconfig.NewKubeconfig(k8scs, log), nil, exec.NewPlainCmdRunner(), log) - app := apppkg.NewApp(appEmpty, apppkg.Hooks{}, fetchFac, tmpFac, deployFac, log, nil) + app := apppkg.NewApp(appEmpty, apppkg.Hooks{}, fetchFac, tmpFac, deployFac, log, nil, nil) out := app.SecretRefs() if len(out) != 0 { diff --git a/pkg/template/cue.go b/pkg/template/cue.go index 5165d5a41..66f5ea572 100644 --- a/pkg/template/cue.go +++ b/pkg/template/cue.go @@ -16,19 +16,20 @@ import ( ) type cue struct { - opts v1alpha1.AppTemplateCue - coreClient kubernetes.Interface - appContext AppContext - cmdRunner exec.CmdRunner + opts v1alpha1.AppTemplateCue + coreClient kubernetes.Interface + appContext AppContext + cmdRunner exec.CmdRunner + additionalValues AdditionalDownwardAPIValues } var _ Template = &cue{} func newCue(opts v1alpha1.AppTemplateCue, appContext AppContext, - coreClient kubernetes.Interface, cmdRunner exec.CmdRunner) *cue { + coreClient kubernetes.Interface, cmdRunner exec.CmdRunner, additionalValues AdditionalDownwardAPIValues) *cue { return &cue{opts: opts, appContext: appContext, - coreClient: coreClient, cmdRunner: cmdRunner} + coreClient: coreClient, cmdRunner: cmdRunner, additionalValues: additionalValues} } // TemplateDir works on directory returning templating result, @@ -59,7 +60,8 @@ func (c *cue) template(dirPath string, input io.Reader) exec.CmdRunResult { args = append(args, c.opts.Paths...) } - vals := Values{c.opts.ValuesFrom, c.appContext, c.coreClient} + vals := Values{c.opts.ValuesFrom, c.additionalValues, c.appContext, c.coreClient} + paths, valuesCleanUpFunc, err := vals.AsPaths(dirPath) if err != nil { return exec.NewCmdRunResultWithErr(fmt.Errorf("Writing values: %w", err)) diff --git a/pkg/template/downward_api_values.go b/pkg/template/downward_api_values.go index 476813237..8b2aeb61c 100644 --- a/pkg/template/downward_api_values.go +++ b/pkg/template/downward_api_values.go @@ -14,28 +14,75 @@ import ( "k8s.io/client-go/util/jsonpath" ) +// AdditionalDownwardAPIValues holds values that are not computed discoverable on the resource itself +type AdditionalDownwardAPIValues struct { + KappControllerVersion func() (string, error) + KubernetesVersion func() (string, error) + KubernetesAPIs func() ([]string, error) +} + // DownwardAPIValues produces multiple key-values based on the DownwardAPI config // queried against the object metadata type DownwardAPIValues struct { - items []v1alpha1.AppTemplateValuesDownwardAPIItem - metadata PartialObjectMetadata + items []v1alpha1.AppTemplateValuesDownwardAPIItem + metadata PartialObjectMetadata + additionalDownwardAPIValues AdditionalDownwardAPIValues } -// AsYAMLs returns many key-values queried (using jsonpath) against an object metadata provided. +// AsYAMLs returns many key-values queried (using jsonpath) against an object metadata provided, and use additionalValues. func (a DownwardAPIValues) AsYAMLs() ([][]byte, error) { dataValues := [][]byte{} + keyValueContent := []byte{} + var err error + for _, item := range a.items { - err := a.validateName(item.Name) + err = a.validateName(item.Name) if err != nil { return nil, err } - fieldPathExpression, err := relaxedJSONPathExpression(item.FieldPath) - if err != nil { - return nil, err + switch { + case item.FieldPath != "": + var fieldPathExpression string + fieldPathExpression, err = relaxedJSONPathExpression(item.FieldPath) + if err != nil { + return nil, err + } + keyValueContent, err = a.extractFieldPathAsKeyValue(item.Name, fieldPathExpression) + case item.KubernetesVersion != nil: + if item.KubernetesVersion.Version == "" { // I wish there was a ternary operator in Go + v, err := a.additionalDownwardAPIValues.KubernetesVersion() + if err != nil { + return nil, err + } + keyValueContent, err = yaml.Marshal(map[string]string{item.Name: v}) + } else { + keyValueContent, err = yaml.Marshal(map[string]string{item.Name: item.KubernetesVersion.Version}) + } + case item.KappControllerVersion != nil: + if item.KappControllerVersion.Version == "" { + v, err := a.additionalDownwardAPIValues.KappControllerVersion() + if err != nil { + return nil, err + } + keyValueContent, err = yaml.Marshal(map[string]string{item.Name: v}) + } else { + keyValueContent, err = yaml.Marshal(map[string]string{item.Name: item.KappControllerVersion.Version}) + } + case item.KubernetesAPIs != nil: + if item.KubernetesAPIs.GroupVersions == nil { + v, err := a.additionalDownwardAPIValues.KubernetesAPIs() + if err != nil { + return nil, err + } + keyValueContent, err = yaml.Marshal(map[string]interface{}{item.Name: v}) + } else { + keyValueContent, err = yaml.Marshal(map[string]interface{}{item.Name: item.KubernetesAPIs.GroupVersions}) + } + default: + return nil, fmt.Errorf("Invalid downward API item given") } - keyValueContent, err := a.extractFieldPathAsKeyValue(item.Name, fieldPathExpression) if err != nil { return nil, err } @@ -89,8 +136,7 @@ func (DownwardAPIValues) nestedKeyValue(name string, val interface{}) ([]byte, e nestedMap = nextLevel } - yamlContent, err := yaml.Marshal(root) - return yamlContent, err + return yaml.Marshal(root) } func splitWithEscaping(s string, separator, escape byte) []string { @@ -107,8 +153,7 @@ func splitWithEscaping(s string, separator, escape byte) []string { token = append(token, s[i]) } } - tokens = append(tokens, string(token)) - return tokens + return append(tokens, string(token)) } // Copied from https://github.com/kubernetes/kubectl/blob/ac26f503e81287d9903761a1a8ded25fdebec6a7/pkg/cmd/get/customcolumn.go#L38 diff --git a/pkg/template/factory.go b/pkg/template/factory.go index 20cf4fb5c..e49e3b91c 100644 --- a/pkg/template/factory.go +++ b/pkg/template/factory.go @@ -26,8 +26,8 @@ func NewFactory(coreClient kubernetes.Interface, fetchFactory fetch.Factory, } // NewYtt returns ytt template. -func (f Factory) NewYtt(opts v1alpha1.AppTemplateYtt, appContext AppContext) *Ytt { - return NewYtt(opts, appContext, f.coreClient, f.fetchFactory, f.cmdRunner) +func (f Factory) NewYtt(opts v1alpha1.AppTemplateYtt, appContext AppContext, additionalValues AdditionalDownwardAPIValues) *Ytt { + return NewYtt(opts, appContext, f.coreClient, f.fetchFactory, f.cmdRunner, additionalValues) } // NewKbld returns kbld template. @@ -37,8 +37,8 @@ func (f Factory) NewKbld(opts v1alpha1.AppTemplateKbld, appContext AppContext) * // NewHelmTemplate returns helm template. func (f Factory) NewHelmTemplate( - opts v1alpha1.AppTemplateHelmTemplate, appContext AppContext) *HelmTemplate { - return NewHelmTemplate(opts, appContext, f.coreClient, f.cmdRunner) + opts v1alpha1.AppTemplateHelmTemplate, appContext AppContext, additionalValues AdditionalDownwardAPIValues) *HelmTemplate { + return NewHelmTemplate(opts, appContext, f.coreClient, f.cmdRunner, additionalValues) } func (f Factory) NewSops( @@ -47,6 +47,6 @@ func (f Factory) NewSops( } // NewCue returns a Cue templater -func (f Factory) NewCue(opts v1alpha1.AppTemplateCue, appContext AppContext) Template { - return newCue(opts, appContext, f.coreClient, f.cmdRunner) +func (f Factory) NewCue(opts v1alpha1.AppTemplateCue, appContext AppContext, additionalValues AdditionalDownwardAPIValues) Template { + return newCue(opts, appContext, f.coreClient, f.cmdRunner, additionalValues) } diff --git a/pkg/template/helm_template.go b/pkg/template/helm_template.go index c5c01ce2d..ff406acb4 100644 --- a/pkg/template/helm_template.go +++ b/pkg/template/helm_template.go @@ -9,6 +9,7 @@ import ( "io" "os" goexec "os/exec" + "strings" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/exec" @@ -17,10 +18,11 @@ import ( ) type HelmTemplate struct { - opts v1alpha1.AppTemplateHelmTemplate - appContext AppContext - coreClient kubernetes.Interface - cmdRunner exec.CmdRunner + opts v1alpha1.AppTemplateHelmTemplate + appContext AppContext + coreClient kubernetes.Interface + cmdRunner exec.CmdRunner + additionalValues AdditionalDownwardAPIValues } // HelmTemplateCmdArgs represents the binary and arguments used during templating @@ -31,13 +33,15 @@ type HelmTemplateCmdArgs struct { var _ Template = &HelmTemplate{} -func NewHelmTemplate(opts v1alpha1.AppTemplateHelmTemplate, - appContext AppContext, coreClient kubernetes.Interface, - cmdRunner exec.CmdRunner) *HelmTemplate { +// NewHelmTemplate returns a HelmTemplate +func NewHelmTemplate(opts v1alpha1.AppTemplateHelmTemplate, appContext AppContext, coreClient kubernetes.Interface, + cmdRunner exec.CmdRunner, additionalValues AdditionalDownwardAPIValues) *HelmTemplate { - return &HelmTemplate{opts, appContext, coreClient, cmdRunner} + return &HelmTemplate{opts: opts, appContext: appContext, coreClient: coreClient, cmdRunner: cmdRunner, + additionalValues: additionalValues} } +// TemplateDir runs helm template against a directory of files func (t *HelmTemplate) TemplateDir(dirPath string) (exec.CmdRunResult, bool) { return t.template(dirPath, nil), true } @@ -70,10 +74,27 @@ func (t *HelmTemplate) template(dirPath string, input io.Reader) exec.CmdRunResu } args := []string{"template", name, chartPath, "--namespace", namespace, "--include-crds"} + vals := Values{t.opts.ValuesFrom, t.additionalValues, t.appContext, t.coreClient} - { // Add values files - vals := Values{t.opts.ValuesFrom, t.appContext, t.coreClient} + var result exec.CmdRunResult + if t.opts.KubernetesVersion != nil { + v, err := vals.AdditionalValues.KubernetesVersion() + if err != nil { + result.AttachErrorf("%s", fmt.Errorf("Unable to get kubernetes version during helm template: %s", err)) + return result + } + args = append(args, []string{"--kube-version", v}...) + } + if t.opts.KubernetesAPIs != nil { + v, err := vals.AdditionalValues.KubernetesAPIs() + if err != nil { + result.AttachErrorf("%s", fmt.Errorf("Unable to get kubernetes APIs during helm template: %s", err)) + return result + } + args = append(args, []string{"--api-versions", strings.Join(v, ",")}...) + } + { paths, valuesCleanUpFunc, err := vals.AsPaths(dirPath) if err != nil { return exec.NewCmdRunResultWithErr(err) @@ -101,7 +122,7 @@ func (t *HelmTemplate) template(dirPath string, input io.Reader) exec.CmdRunResu err := t.cmdRunner.Run(cmd) - result := exec.CmdRunResult{ + result = exec.CmdRunResult{ Stdout: stdoutBs.String(), Stderr: stderrBs.String(), } diff --git a/pkg/template/values.go b/pkg/template/values.go index 1720ba0cb..4079718ce 100644 --- a/pkg/template/values.go +++ b/pkg/template/values.go @@ -6,7 +6,7 @@ package template import ( "context" "fmt" - "io/ioutil" + "os" "sort" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1" @@ -16,7 +16,8 @@ import ( ) type Values struct { - ValuesFrom []v1alpha1.AppTemplateValuesSource + ValuesFrom []v1alpha1.AppTemplateValuesSource + AdditionalValues AdditionalDownwardAPIValues appContext AppContext coreClient kubernetes.Interface @@ -66,8 +67,9 @@ func (t Values) AsPaths(dirPath string) ([]string, func(), error) { case source.DownwardAPI != nil: downwardAPIValues := DownwardAPIValues{ - items: source.DownwardAPI.Items, - metadata: t.appContext.Metadata, + items: source.DownwardAPI.Items, + metadata: t.appContext.Metadata, + additionalDownwardAPIValues: t.AdditionalValues, } paths, err = t.writeFromDownwardAPI(valuesDir.Path(), downwardAPIValues) @@ -134,7 +136,7 @@ func (t Values) writeFile(dstPath, subPath string, content []byte) (string, erro return "", err } - err = ioutil.WriteFile(newPath, content, 0600) + err = os.WriteFile(newPath, content, 0600) if err != nil { return "", fmt.Errorf("Writing file '%s': %s", newPath, err) } diff --git a/pkg/template/values_test.go b/pkg/template/values_test.go index f5820bbfc..0b15628aa 100644 --- a/pkg/template/values_test.go +++ b/pkg/template/values_test.go @@ -209,6 +209,48 @@ func TestValues(t *testing.T) { require.Error(t, err) assert.ErrorContains(t, err, "Writing paths: Invalid field spec provided to DownwardAPI. Only single supported fields are allowed") }) + + t.Run("return kubernetes cluster version if not supplied", func(t *testing.T) { + subject := subject + subject.ValuesFrom = []v1alpha1.AppTemplateValuesSource{{DownwardAPI: &v1alpha1.AppTemplateValuesDownwardAPI{ + Items: []v1alpha1.AppTemplateValuesDownwardAPIItem{ + {Name: "k8s-version", KubernetesVersion: &v1alpha1.Version{}}, + }}, + }} + subject.AdditionalValues = AdditionalDownwardAPIValues{ + KubernetesVersion: func() (string, error) { + return "0.20.0", nil + }, + } + + paths, cleanup, err := subject.AsPaths(os.TempDir()) + require.NoError(t, err) + t.Cleanup(cleanup) + + require.Len(t, paths, 1) + assertFileContents(t, paths[0], "k8s-version: 0.20.0\n") + }) + + t.Run("return kapp-controller version", func(t *testing.T) { + subject := subject + subject.ValuesFrom = []v1alpha1.AppTemplateValuesSource{{DownwardAPI: &v1alpha1.AppTemplateValuesDownwardAPI{ + Items: []v1alpha1.AppTemplateValuesDownwardAPIItem{ + {Name: "kc-version", KappControllerVersion: &v1alpha1.Version{}}, + }}, + }} + subject.AdditionalValues = AdditionalDownwardAPIValues{ + KappControllerVersion: func() (string, error) { + return "0.42.31337", nil + }, + } + + paths, cleanup, err := subject.AsPaths(os.TempDir()) + require.NoError(t, err) + t.Cleanup(cleanup) + + require.Len(t, paths, 1) + assertFileContents(t, paths[0], "kc-version: 0.42.31337\n") + }) }) } diff --git a/pkg/template/ytt.go b/pkg/template/ytt.go index 3e5631df3..12d135bb2 100644 --- a/pkg/template/ytt.go +++ b/pkg/template/ytt.go @@ -17,20 +17,21 @@ import ( ) type Ytt struct { - opts v1alpha1.AppTemplateYtt - appContext AppContext - coreClient kubernetes.Interface - fetchFactory fetch.Factory - cmdRunner exec.CmdRunner + opts v1alpha1.AppTemplateYtt + appContext AppContext + coreClient kubernetes.Interface + fetchFactory fetch.Factory + additionalValues AdditionalDownwardAPIValues + cmdRunner exec.CmdRunner } var _ Template = &Ytt{} // NewYtt returns ytt template. func NewYtt(opts v1alpha1.AppTemplateYtt, appContext AppContext, - coreClient kubernetes.Interface, fetchFactory fetch.Factory, cmdRunner exec.CmdRunner) *Ytt { + coreClient kubernetes.Interface, fetchFactory fetch.Factory, cmdRunner exec.CmdRunner, additionalValues AdditionalDownwardAPIValues) *Ytt { - return &Ytt{opts, appContext, coreClient, fetchFactory, cmdRunner} + return &Ytt{opts: opts, appContext: appContext, coreClient: coreClient, fetchFactory: fetchFactory, cmdRunner: cmdRunner, additionalValues: additionalValues} } func (t *Ytt) TemplateDir(dirPath string) (exec.CmdRunResult, bool) { @@ -60,7 +61,7 @@ func (t *Ytt) template(dirPath string, input io.Reader) exec.CmdRunResult { args = t.addFileMarks(args) { // Add values files - vals := Values{t.opts.ValuesFrom, t.appContext, t.coreClient} + vals := Values{t.opts.ValuesFrom, t.additionalValues, t.appContext, t.coreClient} paths, valuesCleanUpFunc, err := vals.AsPaths(dirPath) if err != nil { diff --git a/test/e2e/kappcontroller/apiserver_protobuf_test.go b/test/e2e/kappcontroller/apiserver_protobuf_test.go index edf2236a5..2bd4afcfe 100644 --- a/test/e2e/kappcontroller/apiserver_protobuf_test.go +++ b/test/e2e/kappcontroller/apiserver_protobuf_test.go @@ -7,8 +7,8 @@ import ( "strings" "testing" - "github.com/vmware-tanzu/carvel-kapp-controller/test/e2e" "github.com/stretchr/testify/require" + "github.com/vmware-tanzu/carvel-kapp-controller/test/e2e" ) func TestAPIServerProtobuf(t *testing.T) { diff --git a/test/e2e/kappcontroller/config_test.go b/test/e2e/kappcontroller/config_test.go index d8f66a707..46bdb9971 100644 --- a/test/e2e/kappcontroller/config_test.go +++ b/test/e2e/kappcontroller/config_test.go @@ -4,15 +4,15 @@ package kappcontroller import ( - "testing" + "fmt" "strings" + "testing" "time" - "fmt" - "github.com/stretchr/testify/assert" - "github.com/vmware-tanzu/carvel-kapp-controller/test/e2e" uitest "github.com/cppforlife/go-cli-ui/ui/test" + "github.com/stretchr/testify/assert" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1" + "github.com/vmware-tanzu/carvel-kapp-controller/test/e2e" "sigs.k8s.io/yaml" ) @@ -226,7 +226,7 @@ stringData: logger.Section("deploy app that fetches content from http server", func() { kapp.RunWithOpts([]string{"deploy", "-f", "-", "-a", name}, e2e.RunOpts{ - StdinReader: strings.NewReader(yaml1), + StdinReader: strings.NewReader(yaml1), OnErrKubectl: []string{"get", "app/test-https", "-oyaml"}, }) @@ -250,7 +250,7 @@ spec: ` kapp.RunWithOpts([]string{"deploy", "-f", "-", "-a", pkgrName}, e2e.RunOpts{ - StdinReader: strings.NewReader(pkgrConfig), + StdinReader: strings.NewReader(pkgrConfig), OnErrKubectl: []string{"get", "pkgr/test-https-pkgr", "-oyaml"}, }) diff --git a/test/e2e/kappcontroller/package_repo_test.go b/test/e2e/kappcontroller/package_repo_test.go index f78154182..0048fe642 100644 --- a/test/e2e/kappcontroller/package_repo_test.go +++ b/test/e2e/kappcontroller/package_repo_test.go @@ -100,7 +100,7 @@ spec: logger.Section("deploy", func() { kapp.RunWithOpts([]string{"deploy", "-f", "-", "-a", name}, e2e.RunOpts{ - StdinReader: strings.NewReader(repoYml), + StdinReader: strings.NewReader(repoYml), OnErrKubectl: []string{"get", "pkgr", "-oyaml"}, }) }) @@ -113,7 +113,7 @@ spec: if err != nil { t.Fatalf("failed to unmarshal: %s", err) } - + expectedStatus := v1alpha1.PackageRepositoryStatus{ Fetch: &kcv1alpha1.AppStatusFetch{ ExitCode: 0, @@ -145,7 +145,7 @@ spec: logger.Section("force a second reconcile and see if it all still works", func() { kapp.RunWithOpts([]string{"deploy", "-f", "-", "-a", name}, e2e.RunOpts{ - StdinReader: strings.NewReader(repoYml+"\n syncPeriod: 30s\n"), + StdinReader: strings.NewReader(repoYml + "\n syncPeriod: 30s\n"), OnErrKubectl: []string{"get", "pkgr", "-oyaml"}, }) }) @@ -186,7 +186,7 @@ spec: out := kubectl.Run([]string{"get", "pkgm/pkg.test.carvel.dev", "-o", "yaml"}) assert.Contains(t, out, "packaging.carvel.dev/package-repository-ref: kappctrl-test/basic.test.carvel.dev") - + verifyPkg("pkg/pkg.test.carvel.dev.1.0.0", "index.docker.io/k8slt/kctrl-example-pkg@sha256:8ffa7f9352149dba1d539d0006b38eda357917edcdd39b82497a61dab2c27b75") verifyPkg("pkg/pkg.test.carvel.dev.2.0.0", "index.docker.io/k8slt/kctrl-example-pkg@sha256:73713d922b5f561c0db2a7ea5f4f6384f7d2d6289886f8400a8aaf5e8fdf134a") }) @@ -371,7 +371,7 @@ spec: logger.Section("deploy pkgr1", func() { kapp.RunWithOpts([]string{"deploy", "-f", "-", "-a", pkgr1Name}, e2e.RunOpts{ - StdinReader: strings.NewReader(pkgr1), + StdinReader: strings.NewReader(pkgr1), OnErrKubectl: []string{"get", "pkgr", "-A", "-oyaml"}, }) assertPkgOwned(t, kubectl, pkgName, pkgr1Name, env.Namespace) @@ -379,7 +379,7 @@ spec: logger.Section("deploy pkgr2 successfully, but pkg is still owned by pkgr1", func() { kapp.RunWithOpts([]string{"deploy", "-f", "-", "-a", pkgr2Name}, e2e.RunOpts{ - StdinReader: strings.NewReader(pkgr2), + StdinReader: strings.NewReader(pkgr2), OnErrKubectl: []string{"get", "pkgr", "-A", "-oyaml"}, }) assertPkgOwned(t, kubectl, pkgName, pkgr1Name, env.Namespace) @@ -424,7 +424,7 @@ spec: logger.Section("updated pkgr1", func() { kapp.RunWithOpts([]string{"deploy", "-f", "-", "-a", pkgr1Name}, e2e.RunOpts{ - StdinReader: strings.NewReader(pkgr1u), + StdinReader: strings.NewReader(pkgr1u), OnErrKubectl: []string{"get", "pkgr", "-A", "-oyaml"}, }) assertPkgOwned(t, kubectl, pkgName, pkgr1Name, env.Namespace) @@ -434,7 +434,7 @@ spec: logger.Section("update pkgr2 successfully, but pkg is still owned by pkgr1", func() { kapp.RunWithOpts([]string{"deploy", "-f", "-", "-a", pkgr2Name}, e2e.RunOpts{ - StdinReader: strings.NewReader(pkgr2u), + StdinReader: strings.NewReader(pkgr2u), OnErrKubectl: []string{"get", "pkgr", "-A", "-oyaml"}, }) assertPkgOwned(t, kubectl, pkgName, pkgr1Name, env.Namespace) @@ -491,7 +491,7 @@ spec: logger.Section("deploy pkgr1", func() { kapp.RunWithOpts([]string{"deploy", "-f", "-", "-a", pkgr1Name}, e2e.RunOpts{ - StdinReader: strings.NewReader(pkgr1), + StdinReader: strings.NewReader(pkgr1), OnErrKubectl: []string{"get", "pkgr", "-A", "-oyaml"}, }) assertPkgOwned(t, kubectl, pkgName, pkgr1Name, env.Namespace) @@ -499,7 +499,7 @@ spec: logger.Section("deploy pkgr2 successfully, but pkg is still owned by pkgr1", func() { kapp.RunWithOpts([]string{"deploy", "-f", "-", "-a", pkgr2Name}, e2e.RunOpts{ - StdinReader: strings.NewReader(pkgr2), + StdinReader: strings.NewReader(pkgr2), OnErrKubectl: []string{"get", "pkgr", "-A", "-oyaml"}, }) assertPkgOwned(t, kubectl, pkgName, pkgr1Name, env.Namespace) @@ -601,7 +601,7 @@ spec: logger.Section("deploy pkgr1 into local namespace", func() { kapp.RunWithOpts([]string{"deploy", "-f", "-", "-a", pkgr1Name}, e2e.RunOpts{ - StdinReader: strings.NewReader(pkgr1), + StdinReader: strings.NewReader(pkgr1), OnErrKubectl: []string{"get", "pkgr", "-A", "-oyaml"}, }) assertPkgOwned(t, kubectl, pkgName, pkgr1Name, pkgr1LocalNS) @@ -609,7 +609,7 @@ spec: logger.Section("deploy pkgr2 into global namespace successfully, but pkg is still owned by pkgr1 in local namespace", func() { kapp.RunWithOpts([]string{"deploy", "-f", "-", "-a", pkgr2Name}, e2e.RunOpts{ - StdinReader: strings.NewReader(pkgr2), + StdinReader: strings.NewReader(pkgr2), OnErrKubectl: []string{"get", "pkgr", "-A", "-oyaml"}, }) assertPkgOwnedWithNs(t, kubectl, pkgName, pkgr1LocalNS, pkgr1Name, pkgr1LocalNS) @@ -620,7 +620,7 @@ spec: kapp.Run([]string{"delete", "-a", pkgr1Name}) kapp.RunWithOpts([]string{"deploy", "-f", "-", "-a", pkgr1Name}, e2e.RunOpts{ - StdinReader: strings.NewReader(pkgr1), + StdinReader: strings.NewReader(pkgr1), OnErrKubectl: []string{"get", "pkgr", "-A", "-oyaml"}, }) assertPkgOwnedWithNs(t, kubectl, pkgName, pkgr2GlobalNS, pkgr2Name, pkgr2GlobalNS) @@ -683,7 +683,7 @@ spec: logger.Section("deploy pkgr1", func() { kapp.RunWithOpts([]string{"deploy", "-f", "-", "-a", pkgr1Name}, e2e.RunOpts{ - StdinReader: strings.NewReader(pkgr1), + StdinReader: strings.NewReader(pkgr1), OnErrKubectl: []string{"get", "pkgr", "-A", "-oyaml"}, }) assertPkgOwned(t, kubectl, pkgName, pkgr1Name, env.Namespace) @@ -691,7 +691,7 @@ spec: logger.Section("deploy pkgr2 successfully, and it overrides bc it has higher revision", func() { kapp.RunWithOpts([]string{"deploy", "-f", "-", "-a", pkgr2Name}, e2e.RunOpts{ - StdinReader: strings.NewReader(fmt.Sprintf(pkgrTemplate, pkgr2Name, pkgName, "2")), + StdinReader: strings.NewReader(fmt.Sprintf(pkgrTemplate, pkgr2Name, pkgName, "2")), OnErrKubectl: []string{"get", "pkgr", "-A", "-oyaml"}, }) assertPkgOwned(t, kubectl, pkgName, pkgr2Name, env.Namespace) @@ -701,7 +701,7 @@ spec: kapp.Run([]string{"delete", "-a", pkgr1Name}) kapp.RunWithOpts([]string{"deploy", "-f", "-", "-a", pkgr1Name}, e2e.RunOpts{ - StdinReader: strings.NewReader(pkgr1), + StdinReader: strings.NewReader(pkgr1), OnErrKubectl: []string{"get", "pkgr", "-A", "-oyaml"}, }) assertPkgOwned(t, kubectl, pkgName, pkgr2Name, env.Namespace) @@ -709,7 +709,7 @@ spec: logger.Section("install pkgr with higher revision using .0 suffix (2.0 > 2)", func() { kapp.RunWithOpts([]string{"deploy", "-f", "-", "-a", pkgr3Name}, e2e.RunOpts{ - StdinReader: strings.NewReader(fmt.Sprintf(pkgrTemplate, pkgr3Name, pkgName, "2.0")), + StdinReader: strings.NewReader(fmt.Sprintf(pkgrTemplate, pkgr3Name, pkgName, "2.0")), OnErrKubectl: []string{"get", "pkgr", "-A", "-oyaml"}, }) assertPkgOwned(t, kubectl, pkgName, pkgr3Name, env.Namespace) @@ -717,7 +717,7 @@ spec: logger.Section("install pkgr with lower revision (2.0 > 1.6.8)", func() { kapp.RunWithOpts([]string{"deploy", "-f", "-", "-a", pkgr4Name}, e2e.RunOpts{ - StdinReader: strings.NewReader(fmt.Sprintf(pkgrTemplate, pkgr4Name, pkgName, "1.6.8")), + StdinReader: strings.NewReader(fmt.Sprintf(pkgrTemplate, pkgr4Name, pkgName, "1.6.8")), OnErrKubectl: []string{"get", "pkgr", "-A", "-oyaml"}, }) assertPkgOwned(t, kubectl, pkgName, pkgr3Name, env.Namespace) @@ -725,7 +725,7 @@ spec: logger.Section("install pkgr with higher revision (2.1.0 > 2.0)", func() { kapp.RunWithOpts([]string{"deploy", "-f", "-", "-a", pkgr5Name}, e2e.RunOpts{ - StdinReader: strings.NewReader(fmt.Sprintf(pkgrTemplate, pkgr5Name, pkgName, "2.1.0")), + StdinReader: strings.NewReader(fmt.Sprintf(pkgrTemplate, pkgr5Name, pkgName, "2.1.0")), OnErrKubectl: []string{"get", "pkgr", "-A", "-oyaml"}, }) assertPkgOwned(t, kubectl, pkgName, pkgr5Name, env.Namespace) @@ -810,7 +810,7 @@ spec: logger.Section("deploy pkgr1", func() { kapp.RunWithOpts([]string{"deploy", "-f", "-", "-a", pkgr1Name}, e2e.RunOpts{ - StdinReader: strings.NewReader(pkgr1), + StdinReader: strings.NewReader(pkgr1), OnErrKubectl: []string{"get", "pkgr", "-A", "-oyaml"}, }) @@ -832,7 +832,7 @@ spec: fmt.Sprintf(pkgTemplate, "contooor.co.uk", "0.22.0")) kapp.RunWithOpts([]string{"deploy", "-f", "-", "-a", pkgr2Name}, e2e.RunOpts{ - StdinReader: strings.NewReader(pkgr2), + StdinReader: strings.NewReader(pkgr2), OnErrKubectl: []string{"get", "pkgr", "-A", "-oyaml"}, }) diff --git a/test/e2e/kappcontroller/template_test.go b/test/e2e/kappcontroller/template_test.go index eb81845e5..2a0f3f02c 100644 --- a/test/e2e/kappcontroller/template_test.go +++ b/test/e2e/kappcontroller/template_test.go @@ -132,6 +132,17 @@ spec: fieldPath: metadata.labels['expectedLabel'] - name: allAnnotations fieldPath: metadata.annotations + - name: kubernetesVersion + kubernetesVersion: + version: 1.0.0 + - name: kcVersion + kappControllerVersion: + version: 2.0.0 + - name: k8sAPIs + kubernetesAPIs: + groupVersions: + - "test/test" + - "test2/test2" deploy: - kapp: {} --- @@ -185,6 +196,11 @@ label: "expectedLabelValue" allAnnotations: expectedAnnotation: expectedAnnotationValue anotherExpectedAnnotation: anotherExpectedAnnotationValue +kubernetesVersion: "1.0.0" +kcVersion: "2.0.0" +k8sAPIs: +- test/test +- test2/test2 `, name, env.Namespace, uid) actual := cm.Data["values"] @@ -226,15 +242,43 @@ spec: cm.cue: | package cm - apiVersion: "v1" - kind: "ConfigMap" - metadata: - name: "cm-result" - data: - value: "cool" + import "encoding/json" + + vals: { + namespace: string + name: string + uid: string + annotation: string + label: string + kubernetesVersion: string + kcVersion: string + k8sAPIs: [...string] + password: string + } + + cm: { + apiVersion: "v1" + kind: "ConfigMap" + metadata: { + name: "cm-result" + } + data: { + value: "cool" + namespace: vals.namespace + name: vals.name + uid: vals.uid + annotation: vals.annotation + label: vals.label + kubernetesVersion: vals.kubernetesVersion + kcVersion: vals.kcVersion + k8sAPIs: json.Marshal(vals.k8sAPIs) + password: vals.password + } + } template: - cue: - inputExpression: "data:" + inputExpression: "vals:" + outputExpression: "cm" valuesFrom: - secretRef: name: secret-values @@ -250,6 +294,17 @@ spec: fieldPath: metadata.annotations['expectedAnnotation'] - name: label fieldPath: metadata.labels['expectedLabel'] + - name: kubernetesVersion + kubernetesVersion: + version: 1.0.0 + - name: kcVersion + kappControllerVersion: + version: 2.0.0 + - name: k8sAPIs + kubernetesAPIs: + groupVersions: + - "test/test" + - "test2/test2" deploy: - kapp: {} --- @@ -270,7 +325,10 @@ stringData: t.Cleanup(cleanUp) logger.Section("deploy", func() { - kapp.RunWithOpts([]string{"deploy", "-f", "-", "-a", name}, e2e.RunOpts{StdinReader: strings.NewReader(appYaml)}) + kapp.RunWithOpts([]string{"deploy", "-f", "-", "-a", name}, e2e.RunOpts{ + StdinReader: strings.NewReader(appYaml), + OnErrKubectl: []string{"get", "app", name, "-oyaml"}, + }) }) logger.Section("check ConfigMap exists", func() { @@ -292,6 +350,9 @@ label: "expectedLabelValue" name: "%s" namespace: "%s" uid: "%s" +kubernetesVersion: "1.0.0" +kcVersion: "2.0.0" +k8sAPIs: "[\"test/test\",\"test2/test2\"]" `, name, env.Namespace, uid) configMapData, err := yaml.Marshal(cm.Data) require.NoError(t, err) @@ -337,10 +398,28 @@ spec: {{- range $k, $v := .Values }} {{ $k }}: {{ $v }} {{- end }} + testchart/templates/versions.yaml: | + {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.Version -}} + apiVersion: v1 + kind: ConfigMap + metadata: + name: k8s-version + annotations: + k8s-version: {{ .Capabilities.KubeVersion.Version }} + {{- end }} + testchart/templates/group-versions-api.yaml: | + {{- if .Capabilities.APIVersions.Has "apps/v1" }} + apiVersion: v1 + kind: ConfigMap + metadata: + name: carvel-group + {{- end }} template: - helmTemplate: path: testchart/ name: testchart + kubernetesVersion: {} + kuberetesAPIs: {} valuesFrom: - secretRef: name: secret-values @@ -408,6 +487,30 @@ uid: "%s" require.NoError(t, err) require.YAMLEq(t, expectedOut, string(actualOut)) }) + + logger.Section("ensuring kubernetes version is templated", func() { + // assume that the presence of the configmap based on semver filtering, then the live k8s version was used + out := kubectl.Run([]string{"get", "configmap", "k8s-version", "-o", "yaml"}) + + var cm corev1.ConfigMap + + err := yaml.Unmarshal([]byte(out), &cm) + if err != nil { + t.Fatalf("Unmarshaling result config map: %s", err) + } + }) + + logger.Section("ensuring kubernetes group/version apis are templated", func() { + // assume that the presence of the configmap based on semver filtering, then the live k8s version was used + out := kubectl.Run([]string{"get", "configmap", "carvel-group", "-o", "yaml"}) + + var cm corev1.ConfigMap + + err := yaml.Unmarshal([]byte(out), &cm) + if err != nil { + t.Fatalf("Unmarshaling result config map: %s", err) + } + }) } func Test_SecretsAndConfigMapsWithCustomPathsCanReconcile(t *testing.T) { diff --git a/test/e2e/secretgencontroller/private_registry_auth_test.go b/test/e2e/secretgencontroller/private_registry_auth_test.go index fbdac9a03..b84eb8997 100644 --- a/test/e2e/secretgencontroller/private_registry_auth_test.go +++ b/test/e2e/secretgencontroller/private_registry_auth_test.go @@ -226,7 +226,7 @@ spec: `, env.Namespace, pkgiName, registryNamespace) + sas.ForNamespaceYAML() kapp.RunWithOpts([]string{"deploy", "-a", pkgiName, "-f", "-"}, e2e.RunOpts{ - StdinReader: strings.NewReader(pkgiYaml), + StdinReader: strings.NewReader(pkgiYaml), OnErrKubectl: []string{"get", "app", "placeholder-private-auth", "-oyaml"}, }) @@ -250,7 +250,7 @@ spec: `, env.Namespace, pkgrName, registryNamespace) + sas.ForNamespaceYAML() kapp.RunWithOpts([]string{"deploy", "-a", pkgrName, "-f", "-"}, e2e.RunOpts{ - StdinReader: strings.NewReader(pkgrYaml), + StdinReader: strings.NewReader(pkgrYaml), OnErrKubectl: []string{"get", "pkgr", "-A", "-oyaml"}, })