public VirtualMachine( string subscriptionId, IVirtualMachine virtualMachine, ILogger logger, IEnumerable <Setting> settings, NotificationDelay notificationDelay, ResourceGroup resourceGroup, LocationTZ timezone, DateTime timeNowUtc ) { this.subscriptionId = subscriptionId; this.virtualMachine = virtualMachine; this.logger = logger; this.settings = settings; this.notificationDelay = notificationDelay; this.resourceGroup = resourceGroup; this.timezones = timezone.GetAll(); this.timeNowUtc = timeNowUtc; // Using the settings, set the start and stop time for the vm startTime = settings.First(s => s.name == "vm_start").value; stopTime = settings.First(s => s.name == "vm_stop").value; SetPowerState(); }
/// <summary> /// Set the timezone that is to be used for time comparison /// The precendence of this is: /// 1. Tags /// 2. Location of virtual machine /// 3. Utc /// </summary> private void SetTimeZone() { // Find the TimeZoneInfo based on the location of the VM LocationTZ vmZone = timezones.First(t => t.name == virtualMachine.RegionName); if (vmZone != null) { zoneInfo = TimeZoneInfo.FindSystemTimeZoneById(vmZone.tzId); } // Set the zone if the vm has a tag that states what zone it is in dynamic tagZone = HasTag("tag_timezone"); if (tagZone) { zoneInfo = TimeZoneInfo.FindSystemTimeZoneById((string)tagZone); } }
public ResourceGroup( IResourceGroup resourceGroup, ILogger logger, IEnumerable <Setting> settings, NotificationDelay notificationDelay, string subscriptionId, LocationTZ timezone, DateTime timeNowUtc ) { this.resourceGroup = resourceGroup; this.logger = logger; this.settings = settings; this.notificationDelay = notificationDelay; this.subscriptionId = subscriptionId; this.timezone = timezone; this.timeNowUtc = timeNowUtc; }
public async Task <bool> Process(DocumentClient client, ILogger log, string id = null) { logger = log; bool error = false; Uri webhook_url = null; string pretext = null; // Create a list of resource group candiates that may be deleted List <string> resource_group_candidates = new List <string>(); // Define a variable to accept the response from SlackClient HttpResponseMessage response = new HttpResponseMessage(); // Get the required settings IEntity setting = new Setting(client, log); IEnumerable <Setting> settings = setting.GetAllByCategory(new string[] { "tags", "slack", "lifecycle" }); bool destroy = Convert.ToBoolean(settings.First(s => s.name == "destroy").value); bool manage_vms = Convert.ToBoolean(settings.First(s => s.name == "manage_vms").value); // Create a connection to Slack // ensure that the webhook url can be turned into a URI try { webhook_url = new Uri(settings.First(s => s.name == "webhook_url").value); } catch { string msg = (String.Format("Unable to create a URI from `webhook_url`: {0}", settings.First(s => s.name == "webhook_url").value)); log.LogInformation(msg); error = true; } if (!error) { SlackClient slackClient = new SlackClient( webhook_url, settings.First(s => s.name == "bot_username").value, settings.First(s => s.name == "icon_url").value, settings.First(s => s.name == "token").value, log, Convert.ToBoolean(settings.First(s => s.name == "slack_enabled").value) ); // Get all the subscriptions that have the reaper enabled on them IEntity subscription = new Subscription(client, log); // dynamic subscriptions = subscription.Get(subscription); IEnumerable <Subscription> subscriptions = subscription.GetUsingSQL("SELECT * FROM subscriptions t WHERE t.enabled = true"); // Get al the timezones LocationTZ timezone = new LocationTZ(client, log); IEnumerable <LocationTZ> timezones = timezone.GetAll(); // Define timestamp to work against so it is the same time for all checks DateTime timeNowUtc = DateTime.UtcNow; // Create a NotificationDelay object to use to // get previoud notification alerts and add new ones NotificationDelay notificationDelay = new NotificationDelay(client, log); // iterate around the subscriptions and check the resource groups and vms in each foreach (Subscription sub in subscriptions) { // Determine the timenow to be used as criteria // This is done on each subscription because there might be a lot of groups to enumerate DateTime _time_now = DateTime.UtcNow; string time_now = _time_now.ToString("yyyy-MM-ddTHH:mm:ssZ"); AzureCredentials credentials = new AzureCredentials( new ServicePrincipalLoginInformation { ClientId = sub.client_id, ClientSecret = sub.client_secret }, sub.tenant_id, AzureEnvironment.AzureGlobalCloud ); Microsoft.Azure.Management.Fluent.Azure azure = Microsoft.Azure.Management.Fluent.Azure.Configure().Authenticate(credentials).WithSubscription(sub.subscription_id); // get a list of resource groups in the subscription IEnumerable <IResourceGroup> resource_groups = azure.ResourceGroups.List(); // iterate around the resource groups foreach (IResourceGroup resource_group in resource_groups) { pretext = null; // if the id is not null check the name and skip if the current is not the right one if (!String.IsNullOrEmpty(id)) { if (resource_group.Name != id) { continue; } } ResourceGroup rg = new ResourceGroup( resource_group, log, settings, notificationDelay, sub.subscription_id, timezone, timeNowUtc ); log.LogInformation("action=reaper, resource_group={resourceGroup}, message=Considering group", resource_group.Name); // Determine if the resource group has expired or not bool notify = rg.ShouldNotify(); bool expired = rg.ShouldDelete(); // if notify is set, send a slack message if (notify) { // Attempt to get the slack userid for the email address await slackClient.GetUserIdByEmail(rg.emailAddress); // Set the properties of the slack message slackClient.AddField("Name", resource_group.Name); slackClient.AddField("Expiry Date", rg.expiryDate.ToString("yyyy-MM-ddTHH:mm:ss")); if (rg.HasTag("tag_alert")) { pretext = "Notification triggered as Resource Group has been tagged with the Alert tag"; } // Add the message to the slack client slackClient.AddAttachmentItem(rg.message, rg.level, pretext); // Send the slack message response = await slackClient.SendMessageAsync(); // Set a record in the notification delay that this resource group has had // a notification sent out notificationDelay.Update(sub.subscription_id, resource_group.Name, "resourceGroup"); } // If the group has expired, delete it // Otherwise work out what machines need to be powered on or off if (expired) { azure.ResourceGroups.DeleteByNameAsync(resource_group.Name); } else { // So that the Slack user is not inundated with messages, ensure that all machines that have been // found to be in violation of running times are bundled up List <string> stopped = new List <string>(); List <string> started = new List <string>(); // as the resource group is valid look at the virtual machines that are running // and determine if they should be shutdown or not IEnumerable <IVirtualMachine> vms = azure.VirtualMachines.ListByResourceGroup(resource_group.Name); foreach (IVirtualMachine vm in vms) { // create a virtualMachine object to work with VirtualMachine virtualMachine = new VirtualMachine( sub.subscription_id, vm, log, settings, notificationDelay, rg, timezone, timeNowUtc ); // Set the power state of the machine VirtualMachine.Status powerStatus = virtualMachine.SetPowerState(); // Using the powerStatus determine what has been started and what has been stopped if (powerStatus == VirtualMachine.Status.Started && !virtualMachine.NotifiedWithinTimePeriod()) { started.Add(virtualMachine.GetName()); } if (powerStatus == VirtualMachine.Status.Stopped && !virtualMachine.NotifiedWithinTimePeriod()) { stopped.Add(virtualMachine.GetName()); } // Add a notification for this machine notificationDelay.Update(sub.subscription_id, resource_group.Name, "virtualMachine", virtualMachine.GetName()); } // only send out messages for VMs if there are some if (vms.Count() > 0 && (started.Count > 0 || stopped.Count > 0) && notify) { // Define the properties of the slack message to send slackClient.AddField("Name", resource_group.Name); slackClient.AddField("VMs Started", String.Join("\n", started)); slackClient.AddField("VMs Stopped", String.Join("\n", stopped)); // Define the message that needs to be attached string message = "The following machines have been stopped or started."; if (!manage_vms) { message += " Reaper is not currently running in active mode, no machines have been stopped or started."; } slackClient.AddAttachmentItem(message, "warning"); // Send a slack message to the owner response = await slackClient.SendMessageAsync(); } } } } } return(true); }
public static async Task <HttpResponseMessage> Run( [HttpTrigger( AuthorizationLevel.Function, "get", "post", Route = "{optype}/{id?}" )] HttpRequest req, string optype, string id, [CosmosDB( ConnectionStringSetting = "azreaper_DOCUMENTDB", CreateIfNotExists = true )] DocumentClient client, ILogger log) { // Intialise variables HttpResponseMessage response = null; ResponseMessage msg = new ResponseMessage(); IEntity entity = null; // Instantiate the necessary class based on the 'optype' switch (optype) { case "setting": entity = new Setting(client, log); break; case "location": entity = new LocationTZ(client, log); break; case "subscription": entity = new Subscription(client, log); break; case "reaper": Reaper reaper = new Reaper(); bool status = await reaper.Process(client, log, id); msg = new ResponseMessage("Reaper executed", false, HttpStatusCode.OK); return(msg.CreateResponse()); default: msg = new ResponseMessage(String.Format("Specified optype is not recognised: {0}", optype), true, HttpStatusCode.NotFound); return(msg.CreateResponse()); } // As this is a REST API use the HTTP Method to determine what is required if (req.Method == "GET") { if (String.IsNullOrEmpty(id)) { IEnumerable <IEntity> result = entity.GetAll(); msg = entity.GetResponse(); response = msg.CreateResponse(result); } else { // Set the identifier on the entity so that a search can be performed IEntity result = entity.Get(id); // Build up the response to return msg = entity.GetResponse(); response = msg.CreateResponse(result); } } else { // Get the JSON string from the body string json = await new StreamReader(req.Body).ReadToEndAsync(); // Peform checks on the paylod to ensure that it is not not null and is valid JSON if (String.IsNullOrWhiteSpace(json)) { msg.SetError("Body of request must not be empty", true, HttpStatusCode.BadRequest); } else if (!IsValidJson(json)) { msg.SetError("Body of request must contain valid JSON data", true, HttpStatusCode.BadRequest); } else { // Parse the json entity.Parse(json); // Determine if there are any errors in the parse msg = entity.GetResponse(); if (!msg.IsError()) { bool status = await entity.Insert(); msg = entity.GetResponse(); } } response = msg.CreateResponse(); } return(response); }