diff --git a/Makefile b/Makefile index 45ddf504..3da3436b 100644 --- a/Makefile +++ b/Makefile @@ -109,9 +109,10 @@ vet: ## Run go vet on the whole project. go vet ./... .PHONY: lint -lint: bin/golangci-lint generate-mocks ## Run linting for the project. +lint: bin/golangci-lint bin/golint generate-mocks ## Run linting for the project. go fmt ./... go vet ./... + golint ./... golangci-lint run -v --timeout 360s ./... @ # The below string of commands checks that ginkgo isn't present in the controllers. @(grep ginkgo ${PROJECT_DIR}/controllers/cloudstack*_controller.go && \ @@ -141,6 +142,8 @@ bin/ginkgo: ## Install ginkgo to bin. GOBIN=$(PROJECT_DIR)/bin go install github.com/onsi/ginkgo/ginkgo@v1.16.5 bin/mockgen: GOBIN=$(PROJECT_DIR)/bin go install github.com/golang/mock/mockgen@v1.6.0 +bin/golint: + GOBIN=$(PROJECT_DIR)/bin go install golang.org/x/lint/golint bin/kustomize: ## Install kustomize to bin. @mkdir -p bin cd bin && curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash diff --git a/api/v1beta1/cloudstackcluster_types.go b/api/v1beta1/cloudstackcluster_types.go index 5eeec390..06c752e6 100644 --- a/api/v1beta1/cloudstackcluster_types.go +++ b/api/v1beta1/cloudstackcluster_types.go @@ -22,15 +22,15 @@ import ( ) const ( - // The presence of a finalizer prevents CAPI from deleting the corresponding CAPI data. + // ClusterFinalizer prevents CAPI from deleting the corresponding CAPI data. ClusterFinalizer = "cloudstackcluster.infrastructure.cluster.x-k8s.io" defaultIdentityRefKind = "Secret" ) -// CloudStackIdentityReference is a reference to an infrastructure +// CloudStackIdentityReference is a reference to an infrastructure. // provider identity to be used to provision cluster resources. type CloudStackIdentityReference struct { - // Kind of the identity. Must be supported by the infrastructure provider + // Kind of the identity. Must be supported by the infrastructure provider. // and may be either cluster or namespace-scoped. // +kubebuilder:validation:MinLength=1 Kind string `json:"kind"` @@ -63,7 +63,7 @@ type CloudStackClusterSpec struct { IdentityRef *CloudStackIdentityReference `json:"identityRef,omitempty"` } -// The status of the abstract CS k8s (not an actual Cloudstack Cluster) cluster. +// CloudStackClusterStatus is the status of the abstract CS k8s (not an actual Cloudstack Cluster) cluster. type CloudStackClusterStatus struct { // Reflects the readiness of the CS cluster. Ready bool `json:"ready"` @@ -90,7 +90,7 @@ type CloudStackClusterStatus struct { //+kubebuilder:object:root=true //+kubebuilder:subresource:status -// CloudStackCluster is the Schema for the cloudstackclusters API +// CloudStackCluster is the Schema for the cloudstackclusters API. type CloudStackCluster struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -103,7 +103,7 @@ type CloudStackCluster struct { //+kubebuilder:object:root=true -// CloudStackClusterList contains a list of CloudStackCluster +// CloudStackClusterList contains a list of CloudStackCluster. type CloudStackClusterList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/api/v1beta1/cloudstackcluster_webhook.go b/api/v1beta1/cloudstackcluster_webhook.go index c38cb26b..e04b2b63 100644 --- a/api/v1beta1/cloudstackcluster_webhook.go +++ b/api/v1beta1/cloudstackcluster_webhook.go @@ -31,6 +31,7 @@ import ( // log is for logging in this package. var cloudstackclusterlog = logf.Log.WithName("cloudstackcluster-resource") +// SetupWebhookWithManager creates a new webhook managed by passed manager. func (r *CloudStackCluster) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). @@ -41,7 +42,7 @@ func (r *CloudStackCluster) SetupWebhookWithManager(mgr ctrl.Manager) error { var _ webhook.Defaulter = &CloudStackCluster{} -// Default implements webhook.Defaulter so a webhook will be registered for the type +// Default implements webhook.Defaulter so a webhook will be registered for the type. func (r *CloudStackCluster) Default() { cloudstackclusterlog.Info("default", "name", r.Name) // No defaulted values supported yet. @@ -51,7 +52,7 @@ func (r *CloudStackCluster) Default() { var _ webhook.Validator = &CloudStackCluster{} -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type. func (r *CloudStackCluster) ValidateCreate() error { cloudstackclusterlog.Info("validate create", "name", r.Name) @@ -66,14 +67,14 @@ func (r *CloudStackCluster) ValidateCreate() error { errorList = append(errorList, field.Required(field.NewPath("spec", "account"), "specifying account requires additionally specifying domain")) } - // Zone and Network are required fields + // Zone and Network are required fields. errorList = webhookutil.EnsureFieldExists(r.Spec.Zone, "Zone", errorList) errorList = webhookutil.EnsureFieldExists(r.Spec.Network, "Network", errorList) return webhookutil.AggregateObjErrors(r.GroupVersionKind().GroupKind(), r.Name, errorList) } -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type. func (r *CloudStackCluster) ValidateUpdate(old runtime.Object) error { cloudstackclusterlog.Info("validate update", "name", r.Name) @@ -93,7 +94,7 @@ func (r *CloudStackCluster) ValidateUpdate(old runtime.Object) error { errorList = append(errorList, field.Forbidden(field.NewPath("spec", "identityRef", "kind"), "must be a Secret")) } - // No spec fields may be changed + // No spec fields may be changed. errorList = webhookutil.EnsureStringFieldsAreEqual(spec.Zone, oldSpec.Zone, "zone", errorList) errorList = webhookutil.EnsureStringFieldsAreEqual(spec.Network, oldSpec.Network, "network", errorList) if oldSpec.ControlPlaneEndpoint.Host != "" { // Need to allow one time endpoint setting via CAPC cluster controller. @@ -110,7 +111,7 @@ func (r *CloudStackCluster) ValidateUpdate(old runtime.Object) error { return webhookutil.AggregateObjErrors(r.GroupVersionKind().GroupKind(), r.Name, errorList) } -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type. func (r *CloudStackCluster) ValidateDelete() error { cloudstackclusterlog.Info("validate delete", "name", r.Name) // No deletion validations. Deletion webhook not enabled. diff --git a/api/v1beta1/cloudstackmachine_types.go b/api/v1beta1/cloudstackmachine_types.go index c200fbc6..cf5b5fe5 100644 --- a/api/v1beta1/cloudstackmachine_types.go +++ b/api/v1beta1/cloudstackmachine_types.go @@ -28,11 +28,11 @@ import ( ) const ( - // The presence of a finalizer prevents CAPI from deleting the corresponding CAPI data. + // MachineFinalizer prevents CAPI from deleting the corresponding CAPI data. MachineFinalizer = "cloudstackmachine.infrastructure.cluster.x-k8s.io" ) -// CloudStackMachineSpec defines the desired state of CloudStackMachine +// CloudStackMachineSpec defines the desired state of CloudStackMachine. type CloudStackMachineSpec struct { // Instance ID. Should only be useful to modify an existing instance. InstanceID *string `json:"instanceID,omitempty"` @@ -47,10 +47,10 @@ type CloudStackMachineSpec struct { // +optional SSHKey string `json:"sshKey"` - // Optional details map for deployVirtualMachine + // Optional details map for deployVirtualMachine. Details map[string]string `json:"details,omitempty"` - // Optional affinitygroupids for deployVirtualMachine + // Optional affinitygroupids for deployVirtualMachine. // +optional AffinityGroupIds []string `json:"affinitygroupids,omitempty"` @@ -59,27 +59,24 @@ type CloudStackMachineSpec struct { // +optional Affinity string `json:"affinity,omitempty"` - // The CS specific unique identifier. Of the form: fmt.Sprintf("cloudstack:///%s", CS Machine Id) + // The CS specific unique identifier. Of the form: fmt.Sprintf("cloudstack:///%s", CS Machine Id). // +optional ProviderID *string `json:"providerID,omitempty"` - // IdentityRef is a reference to a identity to be used when reconciling this cluster + // IdentityRef is a reference to a identity to be used when reconciling this cluster. // +optional // +k8s:conversion-gen=false IdentityRef *CloudStackIdentityReference `json:"identityRef,omitempty"` } -// TODO: Review the use of this field/type. -type InstanceState string - -// Type pulled mostly from the CloudStack API. +// CloudStackMachineStatus type pulled mostly from the CloudStack API. type CloudStackMachineStatus struct { // Addresses contains a CloudStack VM instance's IP addresses. Addresses []corev1.NodeAddress `json:"addresses,omitempty"` // InstanceState is the state of the CloudStack instance for this machine. // +optional - InstanceState InstanceState `json:"instanceState,omitempty"` + InstanceState string `json:"instanceState,omitempty"` // Ready indicates the readiness of the provider resource. Ready bool `json:"ready"` @@ -95,7 +92,7 @@ type CloudStackMachineStatus struct { // +kubebuilder:printcolumn:name="ProviderID",type="string",JSONPath=".spec.providerID",description="CloudStack instance ID" // +kubebuilder:printcolumn:name="Machine",type="string",JSONPath=".metadata.ownerReferences[?(@.kind==\"Machine\")].name",description="Machine object which owns with this CloudStackMachine" -// CloudStackMachine is the Schema for the cloudstackmachines API +// CloudStackMachine is the Schema for the cloudstackmachines API. type CloudStackMachine struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -104,7 +101,7 @@ type CloudStackMachine struct { Status CloudStackMachineStatus `json:"status,omitempty"` } -// The computed affinity group name relevant to this machine. +// AffinityGroupName generates the affinity group name relevant to this machine. func (csm CloudStackMachine) AffinityGroupName( capiMachine *capiv1.Machine, ) (string, error) { @@ -118,7 +115,7 @@ func (csm CloudStackMachine) AffinityGroupName( //+kubebuilder:object:root=true -// CloudStackMachineList contains a list of CloudStackMachine +// CloudStackMachineList contains a list of CloudStackMachine. type CloudStackMachineList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/api/v1beta1/cloudstackmachine_webhook.go b/api/v1beta1/cloudstackmachine_webhook.go index fecc142d..36c11d62 100644 --- a/api/v1beta1/cloudstackmachine_webhook.go +++ b/api/v1beta1/cloudstackmachine_webhook.go @@ -32,6 +32,7 @@ import ( // log is for logging in this package. var cloudstackmachinelog = logf.Log.WithName("cloudstackmachine-resource") +// SetupWebhookWithManager creates a new webhook managed by passed manager. func (r *CloudStackMachine) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). @@ -42,7 +43,7 @@ func (r *CloudStackMachine) SetupWebhookWithManager(mgr ctrl.Manager) error { var _ webhook.Defaulter = &CloudStackMachine{} -// Default implements webhook.Defaulter so a webhook will be registered for the type +// Default implements webhook.Defaulter so a webhook will be registered for the type. func (r *CloudStackMachine) Default() { cloudstackmachinelog.Info("default", "name", r.Name) // No defaulted values supported yet. @@ -52,7 +53,7 @@ func (r *CloudStackMachine) Default() { var _ webhook.Validator = &CloudStackMachine{} -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type. func (r *CloudStackMachine) ValidateCreate() error { cloudstackmachinelog.Info("validate create", "name", r.Name) @@ -69,7 +70,7 @@ func (r *CloudStackMachine) ValidateCreate() error { return webhookutil.AggregateObjErrors(r.GroupVersionKind().GroupKind(), r.Name, errorList) } -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type. func (r *CloudStackMachine) ValidateUpdate(old runtime.Object) error { cloudstackmachinelog.Info("validate update", "name", r.Name) @@ -100,7 +101,7 @@ func (r *CloudStackMachine) ValidateUpdate(old runtime.Object) error { return webhookutil.AggregateObjErrors(r.GroupVersionKind().GroupKind(), r.Name, errorList) } -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type. func (r *CloudStackMachine) ValidateDelete() error { cloudstackmachinelog.Info("validate delete", "name", r.Name) // No deletion validations. Deletion webhook not enabled. diff --git a/api/v1beta1/cloudstackmachinetemplate_types.go b/api/v1beta1/cloudstackmachinetemplate_types.go index d8b332df..2dbddd61 100644 --- a/api/v1beta1/cloudstackmachinetemplate_types.go +++ b/api/v1beta1/cloudstackmachinetemplate_types.go @@ -20,15 +20,16 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// CloudStackMachineTemplateResource defines the desired spec of CloudStackMachine. type CloudStackMachineTemplateResource struct { // Standard object's metadata. - // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata. // +optional ObjectMeta metav1.ObjectMeta `json:"metadata,omitempty"` Spec CloudStackMachineSpec `json:"spec"` } -// CloudStackMachineTemplateSpec defines the desired state of CloudStackMachineTemplate +// CloudStackMachineTemplateSpec defines the desired state of CloudStackMachineTemplate. type CloudStackMachineTemplateSpec struct { Spec CloudStackMachineTemplateResource `json:"template"` } @@ -36,7 +37,7 @@ type CloudStackMachineTemplateSpec struct { //+kubebuilder:object:root=true //+kubebuilder:subresource:status -// CloudStackMachineTemplate is the Schema for the cloudstackmachinetemplates API +// CloudStackMachineTemplate is the Schema for the cloudstackmachinetemplates API. type CloudStackMachineTemplate struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -46,7 +47,7 @@ type CloudStackMachineTemplate struct { //+kubebuilder:object:root=true -// CloudStackMachineTemplateList contains a list of CloudStackMachineTemplate +// CloudStackMachineTemplateList contains a list of CloudStackMachineTemplate. type CloudStackMachineTemplateList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/api/v1beta1/cloudstackmachinetemplate_webhook.go b/api/v1beta1/cloudstackmachinetemplate_webhook.go index b94b64e1..3c2d71d0 100644 --- a/api/v1beta1/cloudstackmachinetemplate_webhook.go +++ b/api/v1beta1/cloudstackmachinetemplate_webhook.go @@ -33,6 +33,7 @@ import ( // log is for logging in this package. var cloudstackmachinetemplatelog = logf.Log.WithName("cloudstackmachinetemplate-resource") +// SetupWebhookWithManager creates a new webhook managed by passed manager. func (r *CloudStackMachineTemplate) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). @@ -42,7 +43,7 @@ func (r *CloudStackMachineTemplate) SetupWebhookWithManager(mgr ctrl.Manager) er //+kubebuilder:webhook:path=/mutate-infrastructure-cluster-x-k8s-io-v1beta1-cloudstackmachinetemplate,mutating=true,failurePolicy=fail,sideEffects=None,groups=infrastructure.cluster.x-k8s.io,resources=cloudstackmachinetemplates,verbs=create;update,versions=v1beta1,name=mcloudstackmachinetemplate.kb.io,admissionReviewVersions=v1beta1 var _ webhook.Defaulter = &CloudStackMachineTemplate{} -// Default implements webhook.Defaulter so a webhook will be registered for the type +// Default implements webhook.Defaulter so a webhook will be registered for the type. func (r *CloudStackMachineTemplate) Default() { cloudstackmachinetemplatelog.Info("default", "name", r.Name) // No defaulted values supported yet. @@ -52,13 +53,13 @@ func (r *CloudStackMachineTemplate) Default() { var _ webhook.Validator = &CloudStackMachineTemplate{} -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type. func (r *CloudStackMachineTemplate) ValidateCreate() error { cloudstackmachinetemplatelog.Info("validate create", "name", r.Name) var ( errorList field.ErrorList - spec = r.Spec.Spec.Spec // CloudStackMachineTemplateSpec.CloudStackMachineTemplateResource.CloudStackMachineSpec + spec = r.Spec.Spec.Spec // CloudStackMachineTemplateSpec.CloudStackMachineTemplateResource.CloudStackMachineSpec. ) // IdentityRefs must be Secrets. @@ -82,13 +83,13 @@ func (r *CloudStackMachineTemplate) ValidateCreate() error { return webhookutil.AggregateObjErrors(r.GroupVersionKind().GroupKind(), r.Name, errorList) } -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type. func (r *CloudStackMachineTemplate) ValidateUpdate(old runtime.Object) error { cloudstackmachinetemplatelog.Info("validate update", "name", r.Name) var ( errorList field.ErrorList - spec = r.Spec.Spec.Spec // CloudStackMachineTemplateSpec.CloudStackMachineTemplateResource.CloudStackMachineSpec + spec = r.Spec.Spec.Spec // CloudStackMachineTemplateSpec.CloudStackMachineTemplateResource.CloudStackMachineSpec. ) oldMachineTemplate, ok := old.(*CloudStackMachineTemplate) @@ -118,7 +119,7 @@ func (r *CloudStackMachineTemplate) ValidateUpdate(old runtime.Object) error { return webhookutil.AggregateObjErrors(r.GroupVersionKind().GroupKind(), r.Name, errorList) } -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type. func (r *CloudStackMachineTemplate) ValidateDelete() error { cloudstackmachinetemplatelog.Info("validate delete", "name", r.Name) // No deletion validations. Deletion webhook not enabled. diff --git a/api/v1beta1/groupversion_info.go b/api/v1beta1/groupversion_info.go index fc503409..89f3384d 100644 --- a/api/v1beta1/groupversion_info.go +++ b/api/v1beta1/groupversion_info.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package v1beta1 contains API Schema definitions for the infrastructure v1beta1 API group +// Package v1beta1 contains API Schema definitions for the infrastructure v1beta1 API group. //+kubebuilder:object:generate=true //+groupName=infrastructure.cluster.x-k8s.io package v1beta1 @@ -25,10 +25,10 @@ import ( ) var ( - // GroupVersion is group version used to register these objects + // GroupVersion is group version used to register these objects. GroupVersion = schema.GroupVersion{Group: "infrastructure.cluster.x-k8s.io", Version: "v1beta1"} - // SchemeBuilder is used to add go types to the GroupVersionKind scheme + // SchemeBuilder is used to add go types to the GroupVersionKind scheme. SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} // AddToScheme adds the types in this group-version to the given scheme. diff --git a/api/v1beta1/webhook_suite_test.go b/api/v1beta1/webhook_suite_test.go index 19551726..be4c2be9 100644 --- a/api/v1beta1/webhook_suite_test.go +++ b/api/v1beta1/webhook_suite_test.go @@ -88,7 +88,7 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) Expect(k8sClient).NotTo(BeNil()) - // start webhook server using Manager + // start webhook server using Manager. webhookInstallOptions := &testEnv.WebhookInstallOptions mgr, err := ctrl.NewManager(cfg, ctrl.Options{ Scheme: scheme, @@ -117,7 +117,7 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) }() - // wait for the webhook server to get ready + // wait for the webhook server to get ready. dialer := &net.Dialer{Timeout: time.Second} addrPort := fmt.Sprintf("%s:%d", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort) Eventually(func() error { diff --git a/controllers/cloudstackcluster_controller.go b/controllers/cloudstackcluster_controller.go index 10a941bc..cb1faf73 100644 --- a/controllers/cloudstackcluster_controller.go +++ b/controllers/cloudstackcluster_controller.go @@ -153,7 +153,7 @@ func (r *CloudStackClusterReconciler) reconcileDelete( return ctrl.Result{}, nil } -// Called in main, this registers the cluster reconciler to the CAPI controller manager. +// SetupWithManager is called in main, this registers the cluster reconciler to the CAPI controller manager. func (r *CloudStackClusterReconciler) SetupWithManager(mgr ctrl.Manager) error { controller, err := ctrl.NewControllerManagedBy(mgr). For(&infrav1.CloudStackCluster{}). @@ -162,16 +162,16 @@ func (r *CloudStackClusterReconciler) SetupWithManager(mgr ctrl.Manager) error { UpdateFunc: func(e event.UpdateEvent) bool { oldCluster := e.ObjectOld.(*infrav1.CloudStackCluster).DeepCopy() newCluster := e.ObjectNew.(*infrav1.CloudStackCluster).DeepCopy() - // Ignore resource version because they are unique + // Ignore resource version because they are unique. oldCluster.ObjectMeta.ResourceVersion = "" newCluster.ObjectMeta.ResourceVersion = "" - // Ignore finalizers updates + // Ignore finalizers updates. oldCluster.ObjectMeta.Finalizers = nil newCluster.ObjectMeta.Finalizers = nil - // Ignore ManagedFields because they are mirror of ObjectMeta + // Ignore ManagedFields because they are mirror of ObjectMeta. oldCluster.ManagedFields = nil newCluster.ManagedFields = nil - // Ignore incremental status updates + // Ignore incremental status updates. oldCluster.Status = infrav1.CloudStackClusterStatus{} newCluster.Status = infrav1.CloudStackClusterStatus{} diff --git a/controllers/cloudstackmachine_controller.go b/controllers/cloudstackmachine_controller.go index b17c129d..9608e7c6 100644 --- a/controllers/cloudstackmachine_controller.go +++ b/controllers/cloudstackmachine_controller.go @@ -46,7 +46,7 @@ import ( capiv1 "sigs.k8s.io/cluster-api/api/v1beta1" ) -// CloudStackMachineReconciler reconciles a CloudStackMachine object +// CloudStackMachineReconciler reconciles a CloudStackMachine object. type CloudStackMachineReconciler struct { client.Client Log logr.Logger @@ -140,7 +140,7 @@ func (r *CloudStackMachineReconciler) Reconcile(ctx context.Context, req ctrl.Re return ctrl.Result{RequeueAfter: requeueTimeout}, nil } - // Reconcile a VM instance for creates/updates + // Reconcile a VM instance for creates/updates. return r.reconcile(ctx, log, csMachine, capiMachine, csCluster) } @@ -236,7 +236,7 @@ func (r *CloudStackMachineReconciler) reconcileDelete( return ctrl.Result{}, nil } -// Called in main, this registers the machine reconciler to the CAPI controller manager. +// SetupWithManager is called in main, this registers the machine reconciler to the CAPI controller manager. func (r *CloudStackMachineReconciler) SetupWithManager(mgr ctrl.Manager) error { controller, err := ctrl.NewControllerManagedBy(mgr). @@ -246,25 +246,25 @@ func (r *CloudStackMachineReconciler) SetupWithManager(mgr ctrl.Manager) error { UpdateFunc: func(e event.UpdateEvent) bool { oldMachine := e.ObjectOld.(*infrav1.CloudStackMachine).DeepCopy() newMachine := e.ObjectNew.(*infrav1.CloudStackMachine).DeepCopy() - // Ignore resource version because they are unique + // Ignore resource version because they are unique. oldMachine.ObjectMeta.ResourceVersion = "" newMachine.ObjectMeta.ResourceVersion = "" - // Ignore generation because it's not used in reconcile + // Ignore generation because it's not used in reconcile. oldMachine.ObjectMeta.Generation = 0 newMachine.ObjectMeta.Generation = 0 - // Ignore finalizers updates + // Ignore finalizers updates. oldMachine.ObjectMeta.Finalizers = nil newMachine.ObjectMeta.Finalizers = nil - // Ignore ManagedFields because they are mirror of ObjectMeta + // Ignore ManagedFields because they are mirror of ObjectMeta. oldMachine.ManagedFields = nil newMachine.ManagedFields = nil - // Ignore incremental status updates + // Ignore incremental status updates. oldMachine.Status = infrav1.CloudStackMachineStatus{} newMachine.Status = infrav1.CloudStackMachineStatus{} - // Ignore provide ID + // Ignore provide ID. oldMachine.Spec.ProviderID = nil newMachine.Spec.ProviderID = nil - // Ignore instance ID + // Ignore instance ID. oldMachine.Spec.InstanceID = nil newMachine.Spec.InstanceID = nil @@ -295,7 +295,7 @@ func (r *CloudStackMachineReconciler) SetupWithManager(mgr ctrl.Manager) error { return err } - // Used below, this maps CAPI clusters to CAPC machines + // Used below, this maps CAPI clusters to CAPC machines. csMachineMapper, err := util.ClusterToObjectsMapper(r.Client, &infrav1.CloudStackMachineList{}, mgr.GetScheme()) if err != nil { return err @@ -319,7 +319,7 @@ func (r *CloudStackMachineReconciler) SetupWithManager(mgr ctrl.Manager) error { ) } -// RemoveManagedAffinity considers a machine's affinity management strategy and removes the created affinity group +// RemoveManagedAffinity considers a machine's affinity management strategy and removes the created affinity group. // if it exists. func (r *CloudStackMachineReconciler) RemoveManagedAffinity( log logr.Logger, diff --git a/controllers/controllers_suite_test.go b/controllers/controllers_suite_test.go index ad5df97a..82d05b81 100644 --- a/controllers/controllers_suite_test.go +++ b/controllers/controllers_suite_test.go @@ -130,7 +130,7 @@ var _ = BeforeSuite(func() { filepath.Join(root, "config", "crd", "bases"), } - // Append CAPI CRDs path + // Append CAPI CRDs path. if capiPath := getFilePathToCAPICRDs(root); capiPath != "" { crdPaths = append(crdPaths, capiPath) } diff --git a/controllers/utils/utils.go b/controllers/utils/utils.go index a424a927..b47f84cf 100644 --- a/controllers/utils/utils.go +++ b/controllers/utils/utils.go @@ -89,7 +89,7 @@ func getKubeadmControlPlaneFromCAPIMachine( // IsOwnerDeleted returns a boolean if the owner of the CAPI machine has been deleted. func IsOwnerDeleted(ctx context.Context, client clientPkg.Client, capiMachine *capiv1.Machine) (bool, error) { if util.IsControlPlaneMachine(capiMachine) { - // The controlplane sticks around after deletion pending the deletion of its machiens. + // The controlplane sticks around after deletion pending the deletion of its machines. // As such, need to check the deletion timestamp thereof. if cp, err := getKubeadmControlPlaneFromCAPIMachine(ctx, client, capiMachine); cp != nil && cp.DeletionTimestamp == nil { return false, nil diff --git a/pkg/cloud/affinity_groups.go b/pkg/cloud/affinity_groups.go index 4385ebfb..4208ebaa 100644 --- a/pkg/cloud/affinity_groups.go +++ b/pkg/cloud/affinity_groups.go @@ -21,17 +21,14 @@ import ( "github.com/pkg/errors" ) -const ( - AntiAffinityGroupType = "host anti-affinity" - AffinityGroupType = "host affinity" -) - +// AffinityGroup type. type AffinityGroup struct { Type string Name string ID string } +// AffinityGroupIface contains the collection of functions for AffinityGroup. type AffinityGroupIface interface { FetchAffinityGroup(*AffinityGroup) error GetOrCreateAffinityGroup(*infrav1.CloudStackCluster, *AffinityGroup) error @@ -44,7 +41,7 @@ func (c *client) FetchAffinityGroup(group *AffinityGroup) (reterr error) { if group.ID != "" { affinityGroup, count, err := c.cs.AffinityGroup.GetAffinityGroupByID(group.ID) if err != nil { - // handle via multierr + // handle via multierr. return err } else if count > 1 { // handle via creating a new error. @@ -58,7 +55,7 @@ func (c *client) FetchAffinityGroup(group *AffinityGroup) (reterr error) { if group.Name != "" { affinityGroup, count, err := c.cs.AffinityGroup.GetAffinityGroupByName(group.Name) if err != nil { - // handle via multierr + // handle via multierr. return err } else if count > 1 { // handle via creating a new error. diff --git a/pkg/cloud/affinity_groups_test.go b/pkg/cloud/affinity_groups_test.go index 584c1f4a..b5f54527 100644 --- a/pkg/cloud/affinity_groups_test.go +++ b/pkg/cloud/affinity_groups_test.go @@ -18,6 +18,7 @@ package cloud_test import ( "errors" + "github.com/apache/cloudstack-go/v2/cloudstack" infrav1 "github.com/aws/cluster-api-provider-cloudstack/api/v1beta1" "github.com/aws/cluster-api-provider-cloudstack/pkg/cloud" @@ -47,7 +48,7 @@ var _ = Describe("AffinityGroup Unit Tests", func() { client = cloud.NewClientFromCSAPIClient(mockClient) fakeAG = &cloud.AffinityGroup{ Name: "FakeAffinityGroup", - Type: cloud.AffinityGroupType} + Type: "host affinity"} cluster = &infrav1.CloudStackCluster{Spec: infrav1.CloudStackClusterSpec{ Zone: "Zone1", Network: "SharedGuestNet1"}} cluster.ObjectMeta.SetUID("0") @@ -89,7 +90,7 @@ var _ = Describe("AffinityGroup Unit Tests", func() { if connectionErr != nil { // Only do these tests if an actual ACS instance is available via cloud-config. Skip("Could not connect to ACS instance.") } - arbitraryAG = &cloud.AffinityGroup{Name: "ArbitraryAffinityGroup", Type: cloud.AffinityGroupType} + arbitraryAG = &cloud.AffinityGroup{Name: "ArbitraryAffinityGroup", Type: "host affinity"} }) AfterEach(func() { mockCtrl.Finish() diff --git a/pkg/cloud/client.go b/pkg/cloud/client.go index 7470b09b..f817008a 100644 --- a/pkg/cloud/client.go +++ b/pkg/cloud/client.go @@ -20,22 +20,17 @@ import ( "strings" "github.com/apache/cloudstack-go/v2/cloudstack" - infrav1 "github.com/aws/cluster-api-provider-cloudstack/api/v1beta1" "github.com/pkg/errors" "gopkg.in/ini.v1" ) //go:generate mockgen -destination=../mocks/mock_client.go -package=mocks github.com/aws/cluster-api-provider-cloudstack/pkg/cloud Client +// Client contains the collection of interfaces for CloudStack API. type Client interface { ClusterIface VMIface - ResolveNetwork(*infrav1.CloudStackCluster) error - GetOrCreateNetwork(*infrav1.CloudStackCluster) error - OpenFirewallRules(*infrav1.CloudStackCluster) error - ResolvePublicIPDetails(*infrav1.CloudStackCluster) (*cloudstack.PublicIpAddress, error) - ResolveLoadBalancerRuleDetails(*infrav1.CloudStackCluster) error - GetOrCreateLoadBalancerRule(*infrav1.CloudStackCluster) error + NetworkIface AffinityGroupIface TagIface } @@ -53,6 +48,7 @@ type config struct { VerifySSL bool `ini:"verify-ssl"` } +// NewClient creates the CloudStack client interface using the cloud-config file. func NewClient(ccPath string) (Client, error) { c := &client{} cfg := &config{VerifySSL: true} @@ -73,6 +69,7 @@ func NewClient(ccPath string) (Client, error) { return c, errors.Wrap(err, "Error encountered while checking CloudStack API Client connectivity.") } +// NewClientFromCSAPIClient is used to test with a mock client. func NewClientFromCSAPIClient(cs *cloudstack.CloudStackClient) Client { c := &client{cs: cs} return c diff --git a/pkg/cloud/cluster.go b/pkg/cloud/cluster.go index 1eddc438..c0b3fa1c 100644 --- a/pkg/cloud/cluster.go +++ b/pkg/cloud/cluster.go @@ -22,6 +22,7 @@ import ( "github.com/pkg/errors" ) +// ClusterIface contains the collection of functions for get/create/delete a cluster. type ClusterIface interface { GetOrCreateCluster(*infrav1.CloudStackCluster) error DisposeClusterResources(cluster *infrav1.CloudStackCluster) error diff --git a/pkg/cloud/cluster_test.go b/pkg/cloud/cluster_test.go index 82a58e52..d722e1b7 100644 --- a/pkg/cloud/cluster_test.go +++ b/pkg/cloud/cluster_test.go @@ -17,6 +17,7 @@ package cloud_test import ( "fmt" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/apache/cloudstack-go/v2/cloudstack" diff --git a/pkg/cloud/helpers.go b/pkg/cloud/helpers.go index 49c878eb..3ebd41e9 100644 --- a/pkg/cloud/helpers.go +++ b/pkg/cloud/helpers.go @@ -30,6 +30,7 @@ func setIfNotEmpty(str string, setFn set) { } } +// CompressAndEncodeString compresses and encodes a string. func CompressAndEncodeString(str string) (string, error) { buf := &bytes.Buffer{} gzipWriter := gzip.NewWriter(buf) diff --git a/pkg/cloud/helpers_test.go b/pkg/cloud/helpers_test.go index b2ea7c5b..b2a4d474 100644 --- a/pkg/cloud/helpers_test.go +++ b/pkg/cloud/helpers_test.go @@ -33,7 +33,7 @@ import ( ) const ( - FixturePath = "test/fixtures/cloud-config-files" + fixturePath = "test/fixtures/cloud-config-files" ) var _ = Describe("Helpers", func() { @@ -65,7 +65,7 @@ var _ = Describe("Helpers", func() { func getConfigPath(filename string) string { dir, _ := os.Getwd() - return path.Join(dir, FixturePath, filename) + return path.Join(dir, fixturePath, filename) } // This matcher is used to make gomega matching compatible with gomock parameter matching. diff --git a/pkg/cloud/instance.go b/pkg/cloud/instance.go index b22b9c85..a814838c 100644 --- a/pkg/cloud/instance.go +++ b/pkg/cloud/instance.go @@ -32,8 +32,13 @@ import ( "k8s.io/utils/pointer" ) -const antiAffinityValue = "anti" +const ( + antiAffinityGroupType = "host anti-affinity" + affinityGroupType = "host affinity" + antiAffinityValue = "anti" +) +// VMIface contains the collection of functions for get/create/delete a VM instance. type VMIface interface { GetOrCreateVMInstance(*infrav1.CloudStackMachine, *capiv1.Machine, *infrav1.CloudStackCluster, string) error ResolveVMInstanceDetails(*infrav1.CloudStackMachine) error @@ -46,7 +51,7 @@ func setMachineDataFromVMMetrics(vmResponse *cloudstack.VirtualMachinesMetric, c csMachine.Spec.ProviderID = pointer.StringPtr(fmt.Sprintf("cloudstack:///%s", vmResponse.Id)) csMachine.Spec.InstanceID = pointer.StringPtr(vmResponse.Id) csMachine.Status.Addresses = []corev1.NodeAddress{{Type: corev1.NodeInternalIP, Address: vmResponse.Ipaddress}} - csMachine.Status.InstanceState = infrav1.InstanceState(vmResponse.State) + csMachine.Status.InstanceState = vmResponse.State } // ResolveVMInstanceDetails Retrieves VM instance details by csMachine.Spec.InstanceID or csMachine.Name, and @@ -67,7 +72,7 @@ func (c *client) ResolveVMInstanceDetails(csMachine *infrav1.CloudStackMachine) // Attempt fetch by name. if csMachine.Name != "" { - vmResp, count, err := c.cs.VirtualMachine.GetVirtualMachinesMetricByName(csMachine.Name) // add opts usage + vmResp, count, err := c.cs.VirtualMachine.GetVirtualMachinesMetricByName(csMachine.Name) // add opts usage. if err != nil && !strings.Contains(strings.ToLower(err.Error()), "no match") { return err } else if count > 1 { @@ -165,27 +170,17 @@ func (c *client) GetOrCreateVMInstance( return err } setIfNotEmpty(compressedAndEncodedUserData, p.SetUserdata) - - if len(csMachine.Spec.AffinityGroupIds) > 0 { - p.SetAffinitygroupids(csMachine.Spec.AffinityGroupIds) - } else if strings.ToLower(csMachine.Spec.Affinity) != "no" && csMachine.Spec.Affinity != "" { - affinityType := AffinityGroupType - if strings.ToLower(csMachine.Spec.Affinity) == antiAffinityValue { - affinityType = AntiAffinityGroupType - } - name, err := csMachine.AffinityGroupName(capiMachine) - if err != nil { - return err - } - group := &AffinityGroup{Name: name, Type: affinityType} - if err := c.GetOrCreateAffinityGroup(csCluster, group); err != nil { - return err - } - p.SetAffinitygroupids([]string{group.ID}) - } setIfNotEmpty(csCluster.Spec.Account, p.SetAccount) setIfNotEmpty(csCluster.Status.DomainID, p.SetDomainid) + affinityGroups, err := c.getAffinityGroupIDs(csMachine, capiMachine, csCluster) + if err != nil { + return err + } + if affinityGroups != nil { + p.SetAffinitygroupids(affinityGroups) + } + // If this VM instance is a control plane, consider setting its IP. _, isControlPlanceMachine := capiMachine.ObjectMeta.Labels["cluster.x-k8s.io/control-plane"] if isControlPlanceMachine && csCluster.Status.NetworkType == NetworkTypeShared { @@ -210,6 +205,34 @@ func (c *client) GetOrCreateVMInstance( } +func (c *client) getAffinityGroupIDs( + csMachine *infrav1.CloudStackMachine, + capiMachine *capiv1.Machine, + csCluster *infrav1.CloudStackCluster) ([]string, error) { + + if len(csMachine.Spec.AffinityGroupIds) > 0 { + return csMachine.Spec.AffinityGroupIds, nil + } + + affinity := strings.ToLower(csMachine.Spec.Affinity) + if affinity != "no" && affinity != "" { + affinityType := affinityGroupType + if affinity == antiAffinityValue { + affinityType = antiAffinityGroupType + } + name, err := csMachine.AffinityGroupName(capiMachine) + if err != nil { + return nil, err + } + group := &AffinityGroup{Name: name, Type: affinityType} + if err := c.GetOrCreateAffinityGroup(csCluster, group); err != nil { + return nil, err + } + return []string{group.ID}, nil + } + return nil, nil +} + // DestroyVMInstance Destroy a VM instance. Assumes machine has been fetched prior and has an instance ID. func (c *client) DestroyVMInstance(csMachine *infrav1.CloudStackMachine) error { diff --git a/pkg/cloud/instance_test.go b/pkg/cloud/instance_test.go index 9808517d..a5a59708 100644 --- a/pkg/cloud/instance_test.go +++ b/pkg/cloud/instance_test.go @@ -18,6 +18,7 @@ package cloud_test import ( "fmt" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" capiv1 "sigs.k8s.io/cluster-api/api/v1beta1" @@ -137,7 +138,7 @@ var _ = Describe("Instance", func() { Ω(client.GetOrCreateVMInstance(csMachine, machine, csCluster, "")).Should(MatchError(unknownErrorMessage)) }) - It("returns errors occuring while fetching sevice offering information", func() { + It("returns errors occurring while fetching service offering information", func() { vms.EXPECT().GetVirtualMachinesMetricByID(*csMachine.Spec.InstanceID).Return(nil, -1, notFoundError) vms.EXPECT().GetVirtualMachinesMetricByName(csMachine.Name).Return(nil, -1, notFoundError) sos.EXPECT().GetServiceOfferingID(csMachine.Spec.Offering).Return("", -1, unknownError) @@ -145,7 +146,7 @@ var _ = Describe("Instance", func() { Ω(client.GetOrCreateVMInstance(csMachine, machine, csCluster, "")).ShouldNot(Succeed()) }) - It("returns errors if more than one sevice offering found", func() { + It("returns errors if more than one service offering found", func() { vms.EXPECT().GetVirtualMachinesMetricByID(*csMachine.Spec.InstanceID).Return(nil, -1, notFoundError) vms.EXPECT().GetVirtualMachinesMetricByName(csMachine.Name).Return(nil, -1, notFoundError) sos.EXPECT().GetServiceOfferingID(csMachine.Spec.Offering).Return("", 2, nil) diff --git a/pkg/cloud/network.go b/pkg/cloud/network.go index 91273845..1f84f344 100644 --- a/pkg/cloud/network.go +++ b/pkg/cloud/network.go @@ -27,13 +27,28 @@ import ( ) const ( - NetOffering = "DefaultIsolatedNetworkOfferingWithSourceNatService" - K8sDefaultAPIPort = 6443 + netOffering = "DefaultIsolatedNetworkOfferingWithSourceNatService" + k8sDefaultAPIPort = 6443 + networkProtocolTCP = "tcp" +) + +const ( + // NetworkTypeIsolated defines isolated network type. NetworkTypeIsolated = "Isolated" - NetworkTypeShared = "Shared" - NetworkProtocolTCP = "tcp" + // NetworkTypeShared defines shared network type. + NetworkTypeShared = "Shared" ) +// NetworkIface contains the collection of functions for network. +type NetworkIface interface { + ResolveNetwork(*infrav1.CloudStackCluster) error + GetOrCreateNetwork(*infrav1.CloudStackCluster) error + OpenFirewallRules(*infrav1.CloudStackCluster) error + ResolvePublicIPDetails(*infrav1.CloudStackCluster) (*cloudstack.PublicIpAddress, error) + ResolveLoadBalancerRuleDetails(*infrav1.CloudStackCluster) error + GetOrCreateLoadBalancerRule(*infrav1.CloudStackCluster) error +} + func (c *client) ResolveNetwork(csCluster *infrav1.CloudStackCluster) (retErr error) { networkID, count, err := c.cs.Network.GetNetworkID(csCluster.Spec.Network) if err != nil { @@ -70,7 +85,7 @@ func (c *client) GetOrCreateNetwork(csCluster *infrav1.CloudStackCluster) (retEr } // Network not found. // Create network since it wasn't found. - offeringID, count, retErr := c.cs.NetworkOffering.GetNetworkOfferingID(NetOffering) + offeringID, count, retErr := c.cs.NetworkOffering.GetNetworkOfferingID(netOffering) if retErr != nil { return retErr } else if count != 1 { @@ -171,7 +186,7 @@ func (c *client) ResolvePublicIPDetails(csCluster *infrav1.CloudStackCluster) (* // Ignore already allocated here since the IP was specified. return publicAddresses.PublicIpAddresses[0], nil } else if publicAddresses.Count > 0 { // Endpoint not specified. - for _, v := range publicAddresses.PublicIpAddresses { // Pick first availabe address. + for _, v := range publicAddresses.PublicIpAddresses { // Pick first available address. if v.Allocated == "" { // Found un-allocated Public IP. return v, nil } @@ -209,7 +224,7 @@ func (c *client) AssociatePublicIPAddress(csCluster *infrav1.CloudStackCluster) } func (c *client) OpenFirewallRules(csCluster *infrav1.CloudStackCluster) (retErr error) { - p := c.cs.Firewall.NewCreateEgressFirewallRuleParams(csCluster.Status.NetworkID, NetworkProtocolTCP) + p := c.cs.Firewall.NewCreateEgressFirewallRuleParams(csCluster.Status.NetworkID, networkProtocolTCP) _, retErr = c.cs.Firewall.CreateEgressFirewallRule(p) if retErr != nil && strings.Contains(strings.ToLower(retErr.Error()), "there is already") { // Already a firewall rule here. retErr = nil @@ -244,13 +259,13 @@ func (c *client) GetOrCreateLoadBalancerRule(csCluster *infrav1.CloudStackCluste } p := c.cs.LoadBalancer.NewCreateLoadBalancerRuleParams( - "roundrobin", "Kubernetes_API_Server", K8sDefaultAPIPort, K8sDefaultAPIPort) + "roundrobin", "Kubernetes_API_Server", k8sDefaultAPIPort, k8sDefaultAPIPort) p.SetNetworkid(csCluster.Status.NetworkID) if csCluster.Spec.ControlPlaneEndpoint.Port != 0 { // Override default public port if endpoint port specified. p.SetPublicport(int(csCluster.Spec.ControlPlaneEndpoint.Port)) } p.SetPublicipid(csCluster.Status.PublicIPID) - p.SetProtocol(NetworkProtocolTCP) + p.SetProtocol(networkProtocolTCP) setIfNotEmpty(csCluster.Spec.Account, p.SetAccount) setIfNotEmpty(csCluster.Status.DomainID, p.SetDomainid) resp, err := c.cs.LoadBalancer.CreateLoadBalancerRule(p) diff --git a/pkg/cloud/tags.go b/pkg/cloud/tags.go index 07b035eb..ec58eef4 100644 --- a/pkg/cloud/tags.go +++ b/pkg/cloud/tags.go @@ -16,6 +16,7 @@ limitations under the License. package cloud +// TagIface contains the collection of functions for add/get/delete tags to a network. type TagIface interface { AddNetworkTags(string, map[string]string) error GetNetworkTags(string) (map[string]string, error) @@ -51,7 +52,7 @@ func (c *client) GetNetworkTags(networkID string) (map[string]string, error) { return tags, nil } -// DeleteNetworkTags deletes matching tags from a network +// DeleteNetworkTags deletes matching tags from a network. func (c *client) DeleteNetworkTags(networkID string, tagsToDelete map[string]string) error { p := c.cs.Resourcetags.NewDeleteTagsParams([]string{networkID}, resourceTypeNetwork) p.SetTags(tagsToDelete) diff --git a/pkg/webhookutil/webhook_validators.go b/pkg/webhookutil/webhook_validators.go index c80ee370..81ffffd8 100644 --- a/pkg/webhookutil/webhook_validators.go +++ b/pkg/webhookutil/webhook_validators.go @@ -24,6 +24,7 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" ) +// EnsureFieldExists checks if a certain value exists. func EnsureFieldExists(value string, name string, allErrs field.ErrorList) field.ErrorList { if value == "" { allErrs = append(allErrs, field.Required(field.NewPath("spec", name), name)) @@ -31,6 +32,7 @@ func EnsureFieldExists(value string, name string, allErrs field.ErrorList) field return allErrs } +// EnsureStringFieldsAreEqual checks if a certain value hasn't changed. func EnsureStringFieldsAreEqual(new string, old string, name string, allErrs field.ErrorList) field.ErrorList { if new != old { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", name), name)) @@ -38,6 +40,7 @@ func EnsureStringFieldsAreEqual(new string, old string, name string, allErrs fie return allErrs } +// EnsureStringStringMapFieldsAreEqual checks if a certain string map hasn't changed. func EnsureStringStringMapFieldsAreEqual(new *map[string]string, old *map[string]string, name string, allErrs field.ErrorList) field.ErrorList { if old == nil && new == nil { return allErrs @@ -51,6 +54,7 @@ func EnsureStringStringMapFieldsAreEqual(new *map[string]string, old *map[string return allErrs } +// AggregateObjErrors aggregates all errors into a new invalid error. func AggregateObjErrors(gk schema.GroupKind, name string, allErrs field.ErrorList) error { if len(allErrs) == 0 { return nil