From e1d338a95304512d889b5ce5e45a8a133ec696a4 Mon Sep 17 00:00:00 2001 From: Erin Corson Date: Wed, 17 Jun 2020 20:07:28 -0600 Subject: [PATCH 1/6] initial working code for app insights api keys --- PROJECT | 80 +++---- api/v1alpha1/appinsightsapikey_types.go | 50 +++++ api/v1alpha1/zz_generated.deepcopy.go | 74 +++++++ config/crd/kustomization.yaml | 3 + .../cainjection_in_appinsightsapikeys.yaml | 8 + .../webhook_in_appinsightsapikeys.yaml | 17 ++ .../rbac/appinsightsapikey_editor_role.yaml | 24 +++ .../rbac/appinsightsapikey_viewer_role.yaml | 20 ++ .../azure_v1alpha1_appinsightsapikey.yaml | 10 + controllers/appinsightsapikey_controller.go | 28 +++ .../appinsightsapikey_controller_test.go | 58 ++++++ controllers/suite_test.go | 19 ++ main.go | 17 ++ .../appinsights/api_keys_client.go | 140 +++++++++++++ .../appinsights/api_keys_reconcile.go | 196 ++++++++++++++++++ .../appinsights/appinsights.go | 4 +- 16 files changed, 707 insertions(+), 41 deletions(-) create mode 100644 api/v1alpha1/appinsightsapikey_types.go create mode 100644 config/crd/patches/cainjection_in_appinsightsapikeys.yaml create mode 100644 config/crd/patches/webhook_in_appinsightsapikeys.yaml create mode 100644 config/rbac/appinsightsapikey_editor_role.yaml create mode 100644 config/rbac/appinsightsapikey_viewer_role.yaml create mode 100644 config/samples/azure_v1alpha1_appinsightsapikey.yaml create mode 100644 controllers/appinsightsapikey_controller.go create mode 100644 controllers/appinsightsapikey_controller_test.go create mode 100644 pkg/resourcemanager/appinsights/api_keys_client.go create mode 100644 pkg/resourcemanager/appinsights/api_keys_reconcile.go diff --git a/PROJECT b/PROJECT index b287247d3de..8f03bf3c9f7 100644 --- a/PROJECT +++ b/PROJECT @@ -1,96 +1,95 @@ -version: "2" domain: microsoft.com repo: github.com/Azure/azure-service-operator resources: - group: azure - version: v1alpha1 kind: StorageAccount -- group: azure version: v1alpha1 - kind: CosmosDB - group: azure + kind: CosmosDB version: v1alpha1 - kind: RedisCache - group: azure + kind: RedisCache version: v1alpha1 - kind: Eventhub - group: azure + kind: Eventhub version: v1alpha1 - kind: ResourceGroup - group: azure + kind: ResourceGroup version: v1alpha1 - kind: EventhubNamespace - group: azure + kind: EventhubNamespace version: v1alpha1 - kind: AzureSqlServer - group: azure + kind: AzureSqlServer version: v1alpha1 - kind: AzureSqlDatabase - group: azure + kind: AzureSqlDatabase version: v1alpha1 - kind: AzureSqlFirewallRule - group: azure + kind: AzureSqlFirewallRule version: v1alpha1 - kind: KeyVault - group: azure + kind: KeyVault version: v1alpha1 - kind: ConsumerGroup - group: azure + kind: ConsumerGroup version: v1alpha1 - kind: AzureSqlAction - group: azure + kind: AzureSqlAction version: v1alpha1 - kind: BlobContainer - group: azure + kind: BlobContainer version: v1alpha1 - kind: PostgreSQLServer - group: azure + kind: PostgreSQLServer version: v1alpha1 - kind: PostgreSQLDatabase - group: azure + kind: PostgreSQLDatabase version: v1alpha1 - kind: PostgreSQLVNetRule - group: azure + kind: PostgreSQLVNetRule version: v1alpha1 +- group: azure kind: PostgreSQLFirewallRule + version: v1alpha1 - group: azure kind: PostgreSQLUser version: v1alpha1 - group: azure version: v1alpha1 - group: azure - version: v1alpha1 kind: ApimService -- group: azure version: v1alpha1 - kind: VirtualNetwork - group: azure + kind: VirtualNetwork version: v1alpha1 - kind: AzurePublicIPAddress - group: azure + kind: AzurePublicIPAddress version: v1alpha1 - kind: AzureNetworkInterface - group: azure + kind: AzureNetworkInterface version: v1alpha1 - kind: AppInsights - group: azure + kind: AppInsights version: v1alpha1 - kind: KeyVaultKey - group: azure + kind: KeyVaultKey version: v1alpha1 - kind: AzureSQLVNetRule - group: azure + kind: AzureSQLVNetRule version: v1alpha1 - kind: MySQLServer - group: azure + kind: MySQLServer version: v1alpha1 - kind: MySQLDatabase - group: azure + kind: MySQLDatabase version: v1alpha1 - kind: MySQLFirewallRule - group: azure + kind: MySQLFirewallRule version: v1alpha1 +- group: azure kind: MySQLVNetRule + version: v1alpha1 - group: azure kind: MySQLUser version: v1alpha1 @@ -98,32 +97,32 @@ resources: kind: AzureVirtualMachine version: v1alpha1 - group: azure - version: v1alpha1 kind: AzureSQLManagedUser -- group: azure version: v1alpha1 - kind: AzureLoadBalancer - group: azure + kind: AzureLoadBalancer version: v1alpha1 +- group: azure kind: AzureVMScaleSet + version: v1alpha1 - group: azure - version: v1beta1 kind: AzureSqlServer -- group: azure version: v1beta1 - kind: AzureSqlDatabase - group: azure + kind: AzureSqlDatabase version: v1beta1 - kind: AzureSqlFirewallRule - group: azure + kind: AzureSqlFirewallRule version: v1beta1 +- group: azure kind: AzureSqlFailoverGroup + version: v1beta1 - group: azure - version: v1alpha2 kind: BlobContainer -- group: azure version: v1alpha2 +- group: azure kind: MySQLServer + version: v1alpha2 - group: azure kind: RedisCacheFirewallRule version: v1alpha1 @@ -133,4 +132,7 @@ resources: - group: azure kind: AzureVirtualMachineExtension version: v1alpha1 - +- group: azure + kind: AppInsightsApiKey + version: v1alpha1 +version: "2" diff --git a/api/v1alpha1/appinsightsapikey_types.go b/api/v1alpha1/appinsightsapikey_types.go new file mode 100644 index 00000000000..077504f9b5f --- /dev/null +++ b/api/v1alpha1/appinsightsapikey_types.go @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// AppInsightsApiKeySpec defines the desired state of AppInsightsApiKey +type AppInsightsApiKeySpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + + ResourceGroup string `json:"resourceGroup"` + AppInsights string `json:"appInsights"` + ReadTelemetry bool `json:"readTelemetry,omitempty"` + WriteAnnotations bool `json:"writeAnnotations,omitempty"` + AuthSDKControlChannel bool `json:"authSDKControlChannel,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status + +// +kubebuilder:printcolumn:name="Provisioned",type="string",JSONPath=".status.provisioned" +// +kubebuilder:printcolumn:name="Message",type="string",JSONPath=".status.message" +// AppInsightsApiKey is the Schema for the appinsightsapikeys API +type AppInsightsApiKey struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec AppInsightsApiKeySpec `json:"spec,omitempty"` + Status ASOStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// AppInsightsApiKeyList contains a list of AppInsightsApiKey +type AppInsightsApiKeyList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []AppInsightsApiKey `json:"items"` +} + +func init() { + SchemeBuilder.Register(&AppInsightsApiKey{}, &AppInsightsApiKeyList{}) +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index f6b4d040db4..df74f74a710 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -267,6 +267,80 @@ func (in *AppInsights) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AppInsightsApiKey) DeepCopyInto(out *AppInsightsApiKey) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppInsightsApiKey. +func (in *AppInsightsApiKey) DeepCopy() *AppInsightsApiKey { + if in == nil { + return nil + } + out := new(AppInsightsApiKey) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AppInsightsApiKey) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AppInsightsApiKeyList) DeepCopyInto(out *AppInsightsApiKeyList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]AppInsightsApiKey, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppInsightsApiKeyList. +func (in *AppInsightsApiKeyList) DeepCopy() *AppInsightsApiKeyList { + if in == nil { + return nil + } + out := new(AppInsightsApiKeyList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AppInsightsApiKeyList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AppInsightsApiKeySpec) DeepCopyInto(out *AppInsightsApiKeySpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppInsightsApiKeySpec. +func (in *AppInsightsApiKeySpec) DeepCopy() *AppInsightsApiKeySpec { + if in == nil { + return nil + } + out := new(AppInsightsApiKeySpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AppInsightsList) DeepCopyInto(out *AppInsightsList) { *out = *in diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 092cba104fa..ffa2eee9227 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -43,6 +43,7 @@ resources: - bases/azure.microsoft.com_rediscacheactions.yaml - bases/azure.microsoft.com_rediscachefirewallrules.yaml - bases/azure.microsoft.com_azurevirtualmachineextensions.yaml +- bases/azure.microsoft.com_appinsightsapikeys.yaml # +kubebuilder:scaffold:crdkustomizeresource patches: @@ -85,6 +86,7 @@ patches: #- patches/webhook_in_rediscachefirewallrules.yaml #- patches/webhook_in_azurevirtualmachineextensions.yaml #- patches/webhook_in_mysqlusers.yaml +#- patches/webhook_in_appinsightsapikeys.yaml # +kubebuilder:scaffold:crdkustomizewebhookpatch # [CAINJECTION] patches here are for enabling the CA injection for each CRD @@ -126,6 +128,7 @@ patches: #- patches/cainjection_in_rediscachefirewallrules.yaml #- patches/cainjection_in_azurevirtualmachineextensions.yaml #- patches/cainjection_in_mysqlusers.yaml +#- patches/cainjection_in_appinsightsapikeys.yaml # +kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. diff --git a/config/crd/patches/cainjection_in_appinsightsapikeys.yaml b/config/crd/patches/cainjection_in_appinsightsapikeys.yaml new file mode 100644 index 00000000000..cfa4fc8b0c6 --- /dev/null +++ b/config/crd/patches/cainjection_in_appinsightsapikeys.yaml @@ -0,0 +1,8 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +# CRD conversion requires k8s 1.13 or later. +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: appinsightsapikeys.azure.microsoft.com diff --git a/config/crd/patches/webhook_in_appinsightsapikeys.yaml b/config/crd/patches/webhook_in_appinsightsapikeys.yaml new file mode 100644 index 00000000000..5dc6c727f9e --- /dev/null +++ b/config/crd/patches/webhook_in_appinsightsapikeys.yaml @@ -0,0 +1,17 @@ +# The following patch enables conversion webhook for CRD +# CRD conversion requires k8s 1.13 or later. +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: appinsightsapikeys.azure.microsoft.com +spec: + conversion: + strategy: Webhook + webhookClientConfig: + # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, + # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) + caBundle: Cg== + service: + namespace: system + name: webhook-service + path: /convert diff --git a/config/rbac/appinsightsapikey_editor_role.yaml b/config/rbac/appinsightsapikey_editor_role.yaml new file mode 100644 index 00000000000..20ec0046ab5 --- /dev/null +++ b/config/rbac/appinsightsapikey_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit appinsightsapikeys. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: appinsightsapikey-editor-role +rules: +- apiGroups: + - azure.microsoft.com + resources: + - appinsightsapikeys + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - azure.microsoft.com + resources: + - appinsightsapikeys/status + verbs: + - get diff --git a/config/rbac/appinsightsapikey_viewer_role.yaml b/config/rbac/appinsightsapikey_viewer_role.yaml new file mode 100644 index 00000000000..01d9377ad5a --- /dev/null +++ b/config/rbac/appinsightsapikey_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view appinsightsapikeys. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: appinsightsapikey-viewer-role +rules: +- apiGroups: + - azure.microsoft.com + resources: + - appinsightsapikeys + verbs: + - get + - list + - watch +- apiGroups: + - azure.microsoft.com + resources: + - appinsightsapikeys/status + verbs: + - get diff --git a/config/samples/azure_v1alpha1_appinsightsapikey.yaml b/config/samples/azure_v1alpha1_appinsightsapikey.yaml new file mode 100644 index 00000000000..ea43e2325eb --- /dev/null +++ b/config/samples/azure_v1alpha1_appinsightsapikey.yaml @@ -0,0 +1,10 @@ +apiVersion: azure.microsoft.com/v1alpha1 +kind: AppInsightsApiKey +metadata: + name: key-sample +spec: + appInsights: appinsights-sample + resourceGroup: resourcegroup-azure-operators + readTelemetry: true + writeAnnotations: true + authSDKControlChannel: true diff --git a/controllers/appinsightsapikey_controller.go b/controllers/appinsightsapikey_controller.go new file mode 100644 index 00000000000..9f226c05ec0 --- /dev/null +++ b/controllers/appinsightsapikey_controller.go @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package controllers + +import ( + ctrl "sigs.k8s.io/controller-runtime" + + azurev1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" +) + +// AppInsightsApiKeyReconciler reconciles a AppInsightsApiKey object +type AppInsightsApiKeyReconciler struct { + Reconciler *AsyncReconciler +} + +// +kubebuilder:rbac:groups=azure.microsoft.com,resources=appinsightsapikeys,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=azure.microsoft.com,resources=appinsightsapikeys/status,verbs=get;update;patch + +func (r *AppInsightsApiKeyReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { + return r.Reconciler.Reconcile(req, &azurev1alpha1.AppInsightsApiKey{}) +} + +func (r *AppInsightsApiKeyReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&azurev1alpha1.AppInsightsApiKey{}). + Complete(r) +} diff --git a/controllers/appinsightsapikey_controller_test.go b/controllers/appinsightsapikey_controller_test.go new file mode 100644 index 00000000000..f1d20735df3 --- /dev/null +++ b/controllers/appinsightsapikey_controller_test.go @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// +build all appinsights + +package controllers + +import ( + "context" + "testing" + + azurev1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestAppInsightsApiKeyController(t *testing.T) { + t.Parallel() + defer PanicRecover(t) + ctx := context.Background() + + rgName := tc.resourceGroupName + rgLocation := tc.resourceGroupLocation + appInsightsName := GenerateTestResourceName("appinsights2") + appInsightsKeyName := GenerateTestResourceName("insightskey") + + // Create an instance of Azure AppInsights + appInsightsInstance := &azurev1alpha1.AppInsights{ + ObjectMeta: metav1.ObjectMeta{ + Name: appInsightsName, + Namespace: "default", + }, + Spec: azurev1alpha1.AppInsightsSpec{ + Kind: "web", + Location: rgLocation, + ResourceGroup: rgName, + ApplicationType: "other", + }, + } + + EnsureInstance(ctx, t, tc, appInsightsInstance) + + apiKey := &azurev1alpha1.AppInsightsApiKey{ + ObjectMeta: metav1.ObjectMeta{ + Name: appInsightsKeyName, + Namespace: "default", + }, + Spec: azurev1alpha1.AppInsightsApiKeySpec{ + AppInsights: appInsightsName, + ResourceGroup: rgName, + ReadTelemetry: true, + WriteAnnotations: true, + }, + } + + EnsureInstance(ctx, t, tc, apiKey) + EnsureDelete(ctx, t, tc, apiKey) + EnsureDelete(ctx, t, tc, appInsightsInstance) +} diff --git a/controllers/suite_test.go b/controllers/suite_test.go index b9e6db85a2b..acafff6f731 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -207,6 +207,25 @@ func setup() error { return err } + err = (&AppInsightsApiKeyReconciler{ + Reconciler: &AsyncReconciler{ + Client: k8sManager.GetClient(), + AzureClient: resourcemanagerappinsights.NewAPIKeyClient( + secretClient, + scheme.Scheme, + ), + Telemetry: telemetry.InitializeTelemetryDefault( + "AppInsightsApiKey", + ctrl.Log.WithName("controllers").WithName("AppInsightsApiKey"), + ), + Recorder: k8sManager.GetEventRecorderFor("AppInsightsApiKey-controller"), + Scheme: scheme.Scheme, + }, + }).SetupWithManager(k8sManager) + if err != nil { + return err + } + err = (&APIMAPIReconciler{ Reconciler: &AsyncReconciler{ Client: k8sManager.GetClient(), diff --git a/main.go b/main.go index 20da1712aea..ab577f58258 100644 --- a/main.go +++ b/main.go @@ -860,6 +860,22 @@ func main() { os.Exit(1) } + if err = (&controllers.AppInsightsApiKeyReconciler{ + Reconciler: &controllers.AsyncReconciler{ + Client: mgr.GetClient(), + AzureClient: resourcemanagerappinsights.NewAPIKeyClient(secretClient, scheme), + Telemetry: telemetry.InitializeTelemetryDefault( + "AppInsightsApiKey", + ctrl.Log.WithName("controllers").WithName("AppInsightsApiKey"), + ), + Recorder: mgr.GetEventRecorderFor("AppInsightsApiKey-controller"), + Scheme: scheme, + }, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "AppInsightsApiKey") + os.Exit(1) + } + if err = (&v1alpha1.AzureSqlServer{}).SetupWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "AzureSqlServer") os.Exit(1) @@ -889,6 +905,7 @@ func main() { setupLog.Error(err, "unable to create webhook", "webhook", "PostgreSQLServer") os.Exit(1) } + // +kubebuilder:scaffold:builder setupLog.Info("starting manager") diff --git a/pkg/resourcemanager/appinsights/api_keys_client.go b/pkg/resourcemanager/appinsights/api_keys_client.go new file mode 100644 index 00000000000..3266b79c986 --- /dev/null +++ b/pkg/resourcemanager/appinsights/api_keys_client.go @@ -0,0 +1,140 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package appinsights + +import ( + "context" + "fmt" + + "github.com/Azure/azure-sdk-for-go/services/appinsights/mgmt/2015-05-01/insights" + "github.com/Azure/azure-service-operator/pkg/resourcemanager/config" + "github.com/Azure/azure-service-operator/pkg/resourcemanager/iam" + "github.com/Azure/azure-service-operator/pkg/secrets" + "k8s.io/apimachinery/pkg/runtime" +) + +type InsightsAPIKeysClient struct { + SecretClient secrets.SecretClient + Scheme *runtime.Scheme +} + +func NewAPIKeyClient(secretClient secrets.SecretClient, scheme *runtime.Scheme) *InsightsAPIKeysClient { + return &InsightsAPIKeysClient{ + SecretClient: secretClient, + Scheme: scheme, + } +} + +func getApiKeysClient() (insights.APIKeysClient, error) { + insightsClient := insights.NewAPIKeysClientWithBaseURI(config.BaseURI(), config.SubscriptionID()) + a, err := iam.GetResourceManagementAuthorizer() + if err != nil { + insightsClient = insights.APIKeysClient{} + return insights.APIKeysClient{}, err + } + + insightsClient.Authorizer = a + insightsClient.AddToUserAgent(config.UserAgent()) + + return insightsClient, err +} + +func (c *InsightsAPIKeysClient) CreateKey(ctx context.Context, resourceGroup, insightsaccount, name string, read, write, authSDK bool) (insights.ApplicationInsightsComponentAPIKey, error) { + apiKey := insights.ApplicationInsightsComponentAPIKey{} + + client, err := getApiKeysClient() + if err != nil { + return apiKey, err + } + + readIds := []string{ + fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/microsoft.insights/components/%s/api", config.SubscriptionID(), resourceGroup, insightsaccount), + fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/microsoft.insights/components/%s/draft", config.SubscriptionID(), resourceGroup, insightsaccount), + fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/microsoft.insights/components/%s/extendqueries", config.SubscriptionID(), resourceGroup, insightsaccount), + fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/microsoft.insights/components/%s/search", config.SubscriptionID(), resourceGroup, insightsaccount), + fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/microsoft.insights/components/%s/aggregate", config.SubscriptionID(), resourceGroup, insightsaccount), + } + + writeIds := []string{ + fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/microsoft.insights/components/%s/annotations", config.SubscriptionID(), resourceGroup, insightsaccount), + } + + authSDKControl := []string{fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/microsoft.insights/components/%s/agentconfig", config.SubscriptionID(), resourceGroup, insightsaccount)} + + keyprops := insights.APIKeyRequest{ + Name: &name, + } + + if read { + keyprops.LinkedReadProperties = &readIds + } + + if write { + keyprops.LinkedWriteProperties = &writeIds + } + + if authSDK { + if keyprops.LinkedReadProperties == nil { + keyprops.LinkedReadProperties = &authSDKControl + } else { + combined := append(*keyprops.LinkedReadProperties, authSDKControl...) + keyprops.LinkedReadProperties = &combined + } + } + + apiKey, err = client.Create( + ctx, + resourceGroup, + insightsaccount, + keyprops, + ) + if err != nil { + return apiKey, err + } + + return apiKey, nil +} + +func (c *InsightsAPIKeysClient) DeleteKey(ctx context.Context, resourceGroup, insightsaccount, name string) error { + client, err := getApiKeysClient() + if err != nil { + return err + } + + _, err = client.Delete(ctx, resourceGroup, insightsaccount, name) + if err != nil { + return err + } + return nil +} + +func (c *InsightsAPIKeysClient) GetKey(ctx context.Context, resourceGroup, insightsaccount, name string) (insights.ApplicationInsightsComponentAPIKey, error) { + result := insights.ApplicationInsightsComponentAPIKey{} + client, err := getApiKeysClient() + if err != nil { + return result, err + } + + result, err = client.Get(ctx, resourceGroup, insightsaccount, name) + if err != nil { + return result, err + } + + return result, nil +} + +func (c *InsightsAPIKeysClient) ListKeys(ctx context.Context, resourceGroup, insightsaccount string) (insights.ApplicationInsightsComponentAPIKeyListResult, error) { + result := insights.ApplicationInsightsComponentAPIKeyListResult{} + client, err := getApiKeysClient() + if err != nil { + return result, err + } + + result, err = client.List(ctx, resourceGroup, insightsaccount) + if err != nil { + return result, err + } + + return result, nil +} diff --git a/pkg/resourcemanager/appinsights/api_keys_reconcile.go b/pkg/resourcemanager/appinsights/api_keys_reconcile.go new file mode 100644 index 00000000000..925c4e2b49d --- /dev/null +++ b/pkg/resourcemanager/appinsights/api_keys_reconcile.go @@ -0,0 +1,196 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package appinsights + +import ( + "context" + "fmt" + "net/http" + "strings" + + "github.com/Azure/azure-service-operator/api/v1alpha1" + azurev1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" + "github.com/Azure/azure-service-operator/pkg/errhelp" + "github.com/Azure/azure-service-operator/pkg/helpers" + "github.com/Azure/azure-service-operator/pkg/resourcemanager" + "github.com/Azure/azure-service-operator/pkg/secrets" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" +) + +func (c *InsightsAPIKeysClient) Ensure(ctx context.Context, obj runtime.Object, opts ...resourcemanager.ConfigOption) (bool, error) { + + instance, err := c.convert(obj) + if err != nil { + return false, err + } + + options := &resourcemanager.Options{} + for _, opt := range opts { + opt(options) + } + + if options.SecretClient != nil { + c.SecretClient = options.SecretClient + } + + instance.Status.Provisioning = true + + // we have reconciled this previously, check if it already exists + if instance.Status.ResourceId != "" { + idParts := strings.Split(instance.Status.ResourceId, "/") + + _, err := c.GetKey( + ctx, + instance.Spec.ResourceGroup, + instance.Spec.AppInsights, + idParts[len(idParts)-1], + ) + if err == nil { + return true, nil + } + + return false, nil + + } + + apiKey, err := c.CreateKey( + ctx, + instance.Spec.ResourceGroup, + instance.Spec.AppInsights, + instance.Name, + instance.Spec.ReadTelemetry, + instance.Spec.WriteAnnotations, + instance.Spec.AuthSDKControlChannel, + ) + if err != nil { + instance.Status.Message = err.Error() + azerr := errhelp.NewAzureErrorAzureError(err) + + // handle 400 errs + if azerr.Code == http.StatusBadRequest { + // if the key already exists it is fine only if the secret exists + if strings.Contains(azerr.Type, "already exists") { + sKey := types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace} + if _, err := c.SecretClient.Get(ctx, sKey); err != nil { + instance.Status.Message = "api key exists but no key could be recovered" + instance.Status.FailedProvisioning = true + } + return true, nil + } + instance.Status.FailedProvisioning = true + return true, nil + } + + return false, fmt.Errorf("api key create error %v", err) + } + + // when create is successful we have to store the apikey somewhere + err = c.SecretClient.Upsert(ctx, + types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace}, + map[string][]byte{"apiKey": []byte(*apiKey.APIKey)}, + secrets.WithOwner(instance), + secrets.WithScheme(c.Scheme), + ) + if err != nil { + instance.Status.Message = "api key created but key was lost before storage" + instance.Status.FailedProvisioning = true + return false, err + } + + // instance.Status.Output = *apiKey.APIKey + instance.Status.Provisioned = true + instance.Status.Provisioning = false + instance.Status.FailedProvisioning = false + instance.Status.Message = resourcemanager.SuccessMsg + instance.Status.ResourceId = *apiKey.ID + + return true, nil +} + +func (c *InsightsAPIKeysClient) Delete(ctx context.Context, obj runtime.Object, opts ...resourcemanager.ConfigOption) (bool, error) { + instance, err := c.convert(obj) + if err != nil { + return false, err + } + + // can't delete without an id and it probably wasn't provisioned by us if it's missing + if instance.Status.ResourceId == "" { + return false, nil + } + + idParts := strings.Split(instance.Status.ResourceId, "/") + + err = c.DeleteKey( + ctx, + instance.Spec.ResourceGroup, + instance.Spec.AppInsights, + idParts[len(idParts)-1], + ) + if err != nil { + catch := []string{ + errhelp.ResourceGroupNotFoundErrorCode, + errhelp.AsyncOpIncompleteError, + } + azerr := errhelp.NewAzureErrorAzureError(err) + if helpers.ContainsString(catch, azerr.Type) { + return false, nil + } + + if azerr.Code == http.StatusNotFound { + return false, nil + } + + return true, fmt.Errorf("ResourceGroup delete error %v", err) + + } + + sKey := types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace} + err = c.SecretClient.Delete(ctx, sKey) + if err != nil { + return true, err + } + + return false, nil +} + +func (c *InsightsAPIKeysClient) GetParents(obj runtime.Object) ([]resourcemanager.KubeParent, error) { + i, err := c.convert(obj) + if err != nil { + return nil, err + } + + return []resourcemanager.KubeParent{ + { + Key: types.NamespacedName{ + Namespace: i.Namespace, + Name: i.Spec.AppInsights, + }, + Target: &v1alpha1.AppInsights{}, + }, + { + Key: types.NamespacedName{ + Namespace: i.Namespace, + Name: i.Spec.ResourceGroup, + }, + Target: &v1alpha1.ResourceGroup{}, + }, + }, nil +} + +func (c *InsightsAPIKeysClient) GetStatus(obj runtime.Object) (*v1alpha1.ASOStatus, error) { + instance, err := c.convert(obj) + if err != nil { + return nil, err + } + return &instance.Status, nil +} + +func (c *InsightsAPIKeysClient) convert(obj runtime.Object) (*azurev1alpha1.AppInsightsApiKey, error) { + local, ok := obj.(*azurev1alpha1.AppInsightsApiKey) + if !ok { + return nil, fmt.Errorf("failed type assertion on kind: %s", obj.GetObjectKind().GroupVersionKind().String()) + } + return local, nil +} diff --git a/pkg/resourcemanager/appinsights/appinsights.go b/pkg/resourcemanager/appinsights/appinsights.go index 86b18a6f9d8..a5c9f627be2 100644 --- a/pkg/resourcemanager/appinsights/appinsights.go +++ b/pkg/resourcemanager/appinsights/appinsights.go @@ -63,8 +63,8 @@ func (m *Manager) GetParents(obj runtime.Object) ([]resourcemanager.KubeParent, }, nil } -func (g *Manager) GetStatus(obj runtime.Object) (*v1alpha1.ASOStatus, error) { - instance, err := g.convert(obj) +func (m *Manager) GetStatus(obj runtime.Object) (*v1alpha1.ASOStatus, error) { + instance, err := m.convert(obj) if err != nil { return nil, err } From ba9fba65594ef7458a19a841b8c88dc0ca94b551 Mon Sep 17 00:00:00 2001 From: Erin Corson Date: Wed, 24 Jun 2020 17:23:17 -0600 Subject: [PATCH 2/6] handle 404 in key creations to account for times when waiting for parent resoruces --- pkg/resourcemanager/appinsights/api_keys_reconcile.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/resourcemanager/appinsights/api_keys_reconcile.go b/pkg/resourcemanager/appinsights/api_keys_reconcile.go index 925c4e2b49d..71a506b4e86 100644 --- a/pkg/resourcemanager/appinsights/api_keys_reconcile.go +++ b/pkg/resourcemanager/appinsights/api_keys_reconcile.go @@ -68,8 +68,9 @@ func (c *InsightsAPIKeysClient) Ensure(ctx context.Context, obj runtime.Object, instance.Status.Message = err.Error() azerr := errhelp.NewAzureErrorAzureError(err) - // handle 400 errs - if azerr.Code == http.StatusBadRequest { + // handle errors + switch azerr.Code { + case http.StatusBadRequest: // if the key already exists it is fine only if the secret exists if strings.Contains(azerr.Type, "already exists") { sKey := types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace} @@ -81,6 +82,8 @@ func (c *InsightsAPIKeysClient) Ensure(ctx context.Context, obj runtime.Object, } instance.Status.FailedProvisioning = true return true, nil + case http.StatusNotFound: + return false, nil } return false, fmt.Errorf("api key create error %v", err) From b8ba892872e22a91d66be983c0a630e1ea2500d0 Mon Sep 17 00:00:00 2001 From: Erin Corson Date: Wed, 24 Jun 2020 17:26:28 -0600 Subject: [PATCH 3/6] repackage helm chart with new crd and role --- ...ppinsightsapikeys.azure.microsoft.com.yaml | 102 ++++++++++++++++++ ...lusterrole_azureoperator-manager-role.yaml | 20 ++++ charts/index.yaml | 6 +- 3 files changed, 125 insertions(+), 3 deletions(-) create mode 100644 charts/azure-service-operator/crds/apiextensions.k8s.io_v1beta1_customresourcedefinition_appinsightsapikeys.azure.microsoft.com.yaml diff --git a/charts/azure-service-operator/crds/apiextensions.k8s.io_v1beta1_customresourcedefinition_appinsightsapikeys.azure.microsoft.com.yaml b/charts/azure-service-operator/crds/apiextensions.k8s.io_v1beta1_customresourcedefinition_appinsightsapikeys.azure.microsoft.com.yaml new file mode 100644 index 00000000000..8141ad40f49 --- /dev/null +++ b/charts/azure-service-operator/crds/apiextensions.k8s.io_v1beta1_customresourcedefinition_appinsightsapikeys.azure.microsoft.com.yaml @@ -0,0 +1,102 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.2.5 + creationTimestamp: null + name: appinsightsapikeys.azure.microsoft.com +spec: + additionalPrinterColumns: + - JSONPath: .status.provisioned + name: Provisioned + type: string + - JSONPath: .status.message + name: Message + type: string + group: azure.microsoft.com + names: + kind: AppInsightsApiKey + listKind: AppInsightsApiKeyList + plural: appinsightsapikeys + singular: appinsightsapikey + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + description: AppInsightsApiKey is the Schema for the appinsightsapikeys API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: AppInsightsApiKeySpec defines the desired state of AppInsightsApiKey + properties: + appInsights: + type: string + authSDKControlChannel: + type: boolean + readTelemetry: + type: boolean + resourceGroup: + type: string + writeAnnotations: + type: boolean + required: + - appInsights + - resourceGroup + type: object + status: + description: ASOStatus (AzureServiceOperatorsStatus) defines the observed + state of resource actions + properties: + completed: + format: date-time + type: string + containsUpdate: + type: boolean + failedProvisioning: + type: boolean + flattenedSecrets: + type: boolean + message: + type: string + output: + type: string + pollingUrl: + type: string + provisioned: + type: boolean + provisioning: + type: boolean + requested: + format: date-time + type: string + resourceId: + type: string + specHash: + type: string + state: + type: string + type: object + type: object + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/charts/azure-service-operator/templates/generated/rbac.authorization.k8s.io_v1_clusterrole_azureoperator-manager-role.yaml b/charts/azure-service-operator/templates/generated/rbac.authorization.k8s.io_v1_clusterrole_azureoperator-manager-role.yaml index 25263dbc4ae..7411ec71dc9 100644 --- a/charts/azure-service-operator/templates/generated/rbac.authorization.k8s.io_v1_clusterrole_azureoperator-manager-role.yaml +++ b/charts/azure-service-operator/templates/generated/rbac.authorization.k8s.io_v1_clusterrole_azureoperator-manager-role.yaml @@ -104,6 +104,26 @@ rules: - get - patch - update +- apiGroups: + - azure.microsoft.com + resources: + - appinsightsapikeys + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - azure.microsoft.com + resources: + - appinsightsapikeys/status + verbs: + - get + - patch + - update - apiGroups: - azure.microsoft.com resources: diff --git a/charts/index.yaml b/charts/index.yaml index 1835476b32c..6b21c3ced68 100644 --- a/charts/index.yaml +++ b/charts/index.yaml @@ -3,14 +3,14 @@ entries: azure-service-operator: - apiVersion: v2 appVersion: 0.1.0 - created: "2020-07-02T13:28:16.308913-06:00" + created: "2020-06-24T17:25:15.791195-06:00" dependencies: - condition: azureUseMI name: aad-pod-identity repository: https://raw.githubusercontent.com/Azure/aad-pod-identity/master/charts version: 1.5.5 description: Deploy components and dependencies of azure-service-operator - digest: be07303de61e0675551bfeae5cf5dd59d944a5dd822f583ebdd0739d6b16567d + digest: a06fcc4b2eb934029da1dd617039435c66998e8482469bb245e37fff41398563 home: https://github.com/Azure/azure-service-operator name: azure-service-operator sources: @@ -18,4 +18,4 @@ entries: urls: - azure-service-operator-0.1.0.tgz version: 0.1.0 -generated: "2020-07-02T13:28:16.303844-06:00" +generated: "2020-06-24T17:25:15.787739-06:00" From 0279cc203ca8559c6a6bbb26488df1e89439dc1e Mon Sep 17 00:00:00 2001 From: Erin Corson Date: Wed, 8 Jul 2020 10:09:10 -0600 Subject: [PATCH 4/6] rebase and update charts --- charts/azure-service-operator-0.1.0.tgz | Bin 26137 -> 26343 bytes charts/index.yaml | 6 +++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/charts/azure-service-operator-0.1.0.tgz b/charts/azure-service-operator-0.1.0.tgz index a3aa3b083411b6a45d675f2771c32d4a6f040600..a9f4fee3c36d65ff8ea42d95b77e8dcb3e3e846a 100644 GIT binary patch delta 25185 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 diff --git a/charts/index.yaml b/charts/index.yaml index 6b21c3ced68..ea7d4271589 100644 --- a/charts/index.yaml +++ b/charts/index.yaml @@ -3,14 +3,14 @@ entries: azure-service-operator: - apiVersion: v2 appVersion: 0.1.0 - created: "2020-06-24T17:25:15.791195-06:00" + created: "2020-07-08T10:09:00.884245-06:00" dependencies: - condition: azureUseMI name: aad-pod-identity repository: https://raw.githubusercontent.com/Azure/aad-pod-identity/master/charts version: 1.5.5 description: Deploy components and dependencies of azure-service-operator - digest: a06fcc4b2eb934029da1dd617039435c66998e8482469bb245e37fff41398563 + digest: 7772dcf19b531eced14cec948a8d2705c19303d5c210ff2f0b52784931baeb19 home: https://github.com/Azure/azure-service-operator name: azure-service-operator sources: @@ -18,4 +18,4 @@ entries: urls: - azure-service-operator-0.1.0.tgz version: 0.1.0 -generated: "2020-06-24T17:25:15.787739-06:00" +generated: "2020-07-08T10:09:00.877634-06:00" From cedbeffb15b5a8b0a55dd23bd2bd10810ca6257d Mon Sep 17 00:00:00 2001 From: Erin Corson Date: Wed, 8 Jul 2020 11:02:18 -0600 Subject: [PATCH 5/6] Update pkg/resourcemanager/appinsights/api_keys_reconcile.go Co-authored-by: Bevan Arps --- pkg/resourcemanager/appinsights/api_keys_reconcile.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/resourcemanager/appinsights/api_keys_reconcile.go b/pkg/resourcemanager/appinsights/api_keys_reconcile.go index 71a506b4e86..1741ce175ed 100644 --- a/pkg/resourcemanager/appinsights/api_keys_reconcile.go +++ b/pkg/resourcemanager/appinsights/api_keys_reconcile.go @@ -37,7 +37,7 @@ func (c *InsightsAPIKeysClient) Ensure(ctx context.Context, obj runtime.Object, instance.Status.Provisioning = true - // we have reconciled this previously, check if it already exists + // we may have reconciled this previously, check if it already exists if instance.Status.ResourceId != "" { idParts := strings.Split(instance.Status.ResourceId, "/") From c8be29723efda9b1371f34703406d308f3831f9d Mon Sep 17 00:00:00 2001 From: Erin Corson Date: Wed, 8 Jul 2020 18:30:02 -0600 Subject: [PATCH 6/6] Update pkg/resourcemanager/appinsights/api_keys_reconcile.go --- pkg/resourcemanager/appinsights/api_keys_reconcile.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/resourcemanager/appinsights/api_keys_reconcile.go b/pkg/resourcemanager/appinsights/api_keys_reconcile.go index 1741ce175ed..a283462cb51 100644 --- a/pkg/resourcemanager/appinsights/api_keys_reconcile.go +++ b/pkg/resourcemanager/appinsights/api_keys_reconcile.go @@ -52,7 +52,6 @@ func (c *InsightsAPIKeysClient) Ensure(ctx context.Context, obj runtime.Object, } return false, nil - } apiKey, err := c.CreateKey(