diff --git a/cmd/aro/operator.go b/cmd/aro/operator.go index 8fdd2060b52..bd960c91e55 100644 --- a/cmd/aro/operator.go +++ b/cmd/aro/operator.go @@ -35,6 +35,7 @@ import ( "github.com/Azure/ARO-RP/pkg/operator/controllers/monitoring" "github.com/Azure/ARO-RP/pkg/operator/controllers/muo" "github.com/Azure/ARO-RP/pkg/operator/controllers/node" + "github.com/Azure/ARO-RP/pkg/operator/controllers/previewfeature" "github.com/Azure/ARO-RP/pkg/operator/controllers/pullsecret" "github.com/Azure/ARO-RP/pkg/operator/controllers/rbac" "github.com/Azure/ARO-RP/pkg/operator/controllers/routefix" @@ -197,6 +198,9 @@ func operator(ctx context.Context, log *logrus.Entry) error { if err = (imageconfig.NewReconciler( arocli, configcli)).SetupWithManager(mgr); err != nil { return fmt.Errorf("unable to create controller ImageConfig: %v", err) + if err = (previewfeature.NewReconciler( + log.WithField("controller", controllers.PreviewFeatureControllerName), + arocli, kubernetescli)).SetupWithManager(mgr); err != nil { } if err = (storageaccounts.NewReconciler( log.WithField("controller", controllers.StorageAccountsControllerName), diff --git a/pkg/operator/controllers/const.go b/pkg/operator/controllers/const.go index 58974552a32..f0fea5f9186 100644 --- a/pkg/operator/controllers/const.go +++ b/pkg/operator/controllers/const.go @@ -7,6 +7,7 @@ const ( AlertwebhookControllerName = "Alertwebhook" AzureSubnetsControllerName = "AzureSubnets" BannerControllerName = "Banner" + PreviewFeatureControllerName = "PreviewFeature" ClusterOperatorAROName = "ClusterOperatorARO" GenevaLoggingControllerName = "GenevaLogging" PullSecretControllerName = "PullSecret" diff --git a/pkg/operator/controllers/previewfeature/nsgflowlogs/nsgflowlogs.go b/pkg/operator/controllers/previewfeature/nsgflowlogs/nsgflowlogs.go new file mode 100644 index 00000000000..89bde058a55 --- /dev/null +++ b/pkg/operator/controllers/previewfeature/nsgflowlogs/nsgflowlogs.go @@ -0,0 +1,48 @@ +package nsgflowlogs + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "context" + + aropreviewv1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/preview.aro.openshift.io/v1alpha1" + + "github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/network" +) + +func NewFeature(flowLogsClient network.FlowLogsClient) *nsgFlowLogsFeature { + return &nsgFlowLogsFeature{ + flowLogsClient: flowLogsClient, + } +} + +type nsgFlowLogsFeature struct { + flowLogsClient network.FlowLogsClient +} + +func (n *nsgFlowLogsFeature) Name() string { + return "nsgFlowLogsFeature" +} + +func (n *nsgFlowLogsFeature) Reconcile(ctx context.Context, instance *aropreviewv1alpha1.PreviewFeature) error { + if instance.Spec.NSGFlowLogs == nil { + return nil + } + + if !instance.Spec.NSGFlowLogs.Enabled { + return n.Disable(instance) + } + + return n.Enable(instance) +} + +func (n *nsgFlowLogsFeature) Enable(instance *aropreviewv1alpha1.PreviewFeature) error { + // TODO: Implement + return nil +} + +func (n *nsgFlowLogsFeature) Disable(instance *aropreviewv1alpha1.PreviewFeature) error { + // TODO: Implement + return nil +} diff --git a/pkg/operator/controllers/previewfeature/previewfeature_controller.go b/pkg/operator/controllers/previewfeature/previewfeature_controller.go new file mode 100644 index 00000000000..a790d0dbfeb --- /dev/null +++ b/pkg/operator/controllers/previewfeature/previewfeature_controller.go @@ -0,0 +1,102 @@ +package previewfeature + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "context" + + "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + aropreviewv1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/preview.aro.openshift.io/v1alpha1" + aroclient "github.com/Azure/ARO-RP/pkg/operator/clientset/versioned" + "github.com/Azure/ARO-RP/pkg/operator/controllers" + "github.com/Azure/ARO-RP/pkg/operator/controllers/previewfeature/nsgflowlogs" + "github.com/Azure/ARO-RP/pkg/util/azureclient" + "github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/network" + "github.com/Azure/ARO-RP/pkg/util/clusterauthorizer" + "github.com/Azure/go-autorest/autorest/azure" +) + +type feature interface { + Name() string + Reconcile(ctx context.Context, instance *aropreviewv1alpha1.PreviewFeature) error +} + +type Reconciler struct { + log *logrus.Entry + + arocli aroclient.Interface + kubernetescli kubernetes.Interface +} + +func NewReconciler(log *logrus.Entry, arocli aroclient.Interface, kubernetescli kubernetes.Interface) *Reconciler { + return &Reconciler{ + log: log, + arocli: arocli, + kubernetescli: kubernetescli, + } +} + +// Reconcile reconciles ARO preview features +func (r *Reconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) { + instance, err := r.arocli.PreviewV1alpha1().PreviewFeatures().Get(ctx, aropreviewv1alpha1.SingletonPreviewFeatureName, metav1.GetOptions{}) + if err != nil { + return reconcile.Result{}, err + } + + // Get endpoints from operator + azEnv, err := azureclient.EnvironmentFromName(instance.Spec.AZEnvironment) + if err != nil { + return reconcile.Result{}, err + } + + resource, err := azure.ParseResourceID(instance.Spec.ResourceID) + if err != nil { + return reconcile.Result{}, err + } + + // create refreshable authorizer from token + authorizer, err := clusterauthorizer.NewAzRefreshableAuthorizer(ctx, r.log, &azEnv, r.kubernetescli) + if err != nil { + return reconcile.Result{}, err + } + + flowLogsClient := network.NewFlowLogsClient(&azEnv, resource.SubscriptionID, authorizer) + + features := []feature{ + nsgflowlogs.NewFeature(flowLogsClient), + } + + err = nil + for _, f := range features { + thisErr := f.Reconcile(ctx, instance) + if thisErr != nil { + // Reconcile all features even if there is an error in some of them + err = thisErr + r.log.Errorf("error reconciling %q: %s", f.Name(), err) + } + } + + // Controller-runtime will requeue when err != nil + return reconcile.Result{}, err +} + +// SetupWithManager setup our manager +func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { + aroPreviewFeaturePredicate := predicate.NewPredicateFuncs(func(o client.Object) bool { + return o.GetName() == aropreviewv1alpha1.SingletonPreviewFeatureName + }) + + return ctrl.NewControllerManagedBy(mgr). + For(&aropreviewv1alpha1.PreviewFeature{}, builder.WithPredicates(aroPreviewFeaturePredicate)). + Named(controllers.PreviewFeatureControllerName). + Complete(r) +} diff --git a/pkg/util/azureclient/mgmt/network/flowlogs.go b/pkg/util/azureclient/mgmt/network/flowlogs.go new file mode 100644 index 00000000000..e0e5461b47f --- /dev/null +++ b/pkg/util/azureclient/mgmt/network/flowlogs.go @@ -0,0 +1,32 @@ +package network + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + mgmtnetwork "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2020-08-01/network" + "github.com/Azure/go-autorest/autorest" + + "github.com/Azure/ARO-RP/pkg/util/azureclient" +) + +// FlowLogsClient is a minimal interface for azure FlowLogsClient +type FlowLogsClient interface { + FlowLogsClientAddons +} + +type flowLogsClient struct { + mgmtnetwork.FlowLogsClient +} + +var _ FlowLogsClient = &flowLogsClient{} + +// NewFlowLogsClient creates a new FlowLogsClient +func NewFlowLogsClient(environment *azureclient.AROEnvironment, tenantID string, authorizer autorest.Authorizer) FlowLogsClient { + client := mgmtnetwork.NewFlowLogsClientWithBaseURI(environment.ResourceManagerEndpoint, tenantID) + client.Authorizer = authorizer + + return &flowLogsClient{ + FlowLogsClient: client, + } +} diff --git a/pkg/util/azureclient/mgmt/network/flowlogs_addons.go b/pkg/util/azureclient/mgmt/network/flowlogs_addons.go new file mode 100644 index 00000000000..f75cbce0700 --- /dev/null +++ b/pkg/util/azureclient/mgmt/network/flowlogs_addons.go @@ -0,0 +1,24 @@ +package network + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "context" + + mgmtnetwork "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2020-08-01/network" +) + +// FlowLogsClientAddons contains addons to WatchersClient +type FlowLogsClientAddons interface { + CreateOrUpdateAndWait(ctx context.Context, resourceGroupName string, networkWatcherName string, flowLogName string, parameters mgmtnetwork.FlowLog) error +} + +func (c *flowLogsClient) CreateOrUpdateAndWait(ctx context.Context, resourceGroupName string, networkWatcherName string, flowLogName string, parameters mgmtnetwork.FlowLog) error { + future, err := c.FlowLogsClient.CreateOrUpdate(ctx, resourceGroupName, networkWatcherName, flowLogName, parameters) + if err != nil { + return err + } + + return future.WaitForCompletionRef(ctx, c.Client) +} diff --git a/pkg/util/azureclient/mgmt/network/generate.go b/pkg/util/azureclient/mgmt/network/generate.go index 97e8bf96c1e..dd7949d49d1 100644 --- a/pkg/util/azureclient/mgmt/network/generate.go +++ b/pkg/util/azureclient/mgmt/network/generate.go @@ -4,5 +4,5 @@ package network // Licensed under the Apache License 2.0. //go:generate rm -rf ../../../../util/mocks/$GOPACKAGE -//go:generate go run ../../../../../vendor/github.com/golang/mock/mockgen -destination=../../../../util/mocks/azureclient/mgmt/$GOPACKAGE/$GOPACKAGE.go github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/$GOPACKAGE InterfacesClient,LoadBalancersClient,PrivateEndpointsClient,PrivateLinkServicesClient,PublicIPAddressesClient,RouteTablesClient,SubnetsClient,VirtualNetworksClient,SecurityGroupsClient,VirtualNetworkPeeringsClient,UsageClient +//go:generate go run ../../../../../vendor/github.com/golang/mock/mockgen -destination=../../../../util/mocks/azureclient/mgmt/$GOPACKAGE/$GOPACKAGE.go github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/$GOPACKAGE InterfacesClient,LoadBalancersClient,PrivateEndpointsClient,PrivateLinkServicesClient,PublicIPAddressesClient,RouteTablesClient,SubnetsClient,VirtualNetworksClient,SecurityGroupsClient,VirtualNetworkPeeringsClient,UsageClient,FlowLogsClient //go:generate go run ../../../../../vendor/golang.org/x/tools/cmd/goimports -local=github.com/Azure/ARO-RP -e -w ../../../../util/mocks/azureclient/mgmt/$GOPACKAGE/$GOPACKAGE.go diff --git a/pkg/util/mocks/azureclient/mgmt/network/network.go b/pkg/util/mocks/azureclient/mgmt/network/network.go index 8670f18a7df..e461682a319 100644 --- a/pkg/util/mocks/azureclient/mgmt/network/network.go +++ b/pkg/util/mocks/azureclient/mgmt/network/network.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/network (interfaces: InterfacesClient,LoadBalancersClient,PrivateEndpointsClient,PrivateLinkServicesClient,PublicIPAddressesClient,RouteTablesClient,SubnetsClient,VirtualNetworksClient,SecurityGroupsClient,VirtualNetworkPeeringsClient,UsageClient) +// Source: github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/network (interfaces: InterfacesClient,LoadBalancersClient,PrivateEndpointsClient,PrivateLinkServicesClient,PublicIPAddressesClient,RouteTablesClient,SubnetsClient,VirtualNetworksClient,SecurityGroupsClient,VirtualNetworkPeeringsClient,UsageClient,FlowLogsClient) // Package mock_network is a generated GoMock package. package mock_network @@ -642,3 +642,40 @@ func (mr *MockUsageClientMockRecorder) List(arg0, arg1 interface{}) *gomock.Call mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockUsageClient)(nil).List), arg0, arg1) } + +// MockFlowLogsClient is a mock of FlowLogsClient interface. +type MockFlowLogsClient struct { + ctrl *gomock.Controller + recorder *MockFlowLogsClientMockRecorder +} + +// MockFlowLogsClientMockRecorder is the mock recorder for MockFlowLogsClient. +type MockFlowLogsClientMockRecorder struct { + mock *MockFlowLogsClient +} + +// NewMockFlowLogsClient creates a new mock instance. +func NewMockFlowLogsClient(ctrl *gomock.Controller) *MockFlowLogsClient { + mock := &MockFlowLogsClient{ctrl: ctrl} + mock.recorder = &MockFlowLogsClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockFlowLogsClient) EXPECT() *MockFlowLogsClientMockRecorder { + return m.recorder +} + +// CreateOrUpdateAndWait mocks base method. +func (m *MockFlowLogsClient) CreateOrUpdateAndWait(arg0 context.Context, arg1, arg2, arg3 string, arg4 network.FlowLog) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateOrUpdateAndWait", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateOrUpdateAndWait indicates an expected call of CreateOrUpdateAndWait. +func (mr *MockFlowLogsClientMockRecorder) CreateOrUpdateAndWait(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOrUpdateAndWait", reflect.TypeOf((*MockFlowLogsClient)(nil).CreateOrUpdateAndWait), arg0, arg1, arg2, arg3, arg4) +}