Ejemplo n.º 1
0
        public async Task <IChangeSummary> AddOrUpdateOrganizationWebhooks(ShipHubContext context, IGitHubOrganizationAdmin admin)
        {
            var changes = ChangeSummary.Empty;

            var hook = await context.Hooks.AsNoTracking().SingleOrDefaultAsync(x => x.OrganizationId == _orgId);

            context.Database.Connection.Close();

            // If our last operation on this repo hook resulted in error, delay.
            if (hook?.LastError != null && hook?.LastError.Value > DateTimeOffset.UtcNow.Subtract(HookErrorDelay))
            {
                return(changes); // Wait to try later.
            }

            // There are now a few cases to handle
            // If there is no record of a hook, try to make one.
            // If there is an incomplete record, try to make it.
            // If there is an errored record, sleep or retry
            if (hook?.GitHubId == null)
            {
                // GitHub will immediately send a ping when the webhook is created.
                // To avoid any chance for a race, add the Hook to the DB first, then
                // create on GitHub.
                HookTableType newHook = null;
                if (hook == null)
                {
                    newHook = await context.CreateHook(Guid.NewGuid(), string.Join(",", RequiredEvents), organizationId : _orgId);
                }
                else
                {
                    newHook = new HookTableType()
                    {
                        Id     = hook.Id,
                        Secret = hook.Secret,
                        Events = string.Join(",", RequiredEvents),
                    };
                }

                // Assume failure until we succeed
                newHook.LastError = DateTimeOffset.UtcNow;

                try {
                    var hookList = await admin.OrganizationWebhooks(_login);

                    if (!hookList.IsOk)
                    {
                        this.Info($"Unable to list hooks for {_login}. {hookList.Status} {hookList.Error}");
                        return(changes);
                    }

                    var existingHooks = hookList.Result
                                        .Where(x => x.Name.Equals("web"))
                                        .Where(x => x.Config.Url.StartsWith($"https://{_apiHostName}/", StringComparison.OrdinalIgnoreCase));

                    // Delete any existing hooks that already point back to us - don't
                    // want to risk adding multiple Ship hooks.
                    foreach (var existingHook in existingHooks)
                    {
                        var deleteResponse = await admin.DeleteOrganizationWebhook(_login, existingHook.Id);

                        if (!deleteResponse.Succeeded)
                        {
                            this.Info($"Failed to delete existing hook ({existingHook.Id}) for org '{_login}' {deleteResponse.Status} {deleteResponse.Error}");
                        }
                    }

                    var addHookResponse = await admin.AddOrganizationWebhook(
                        _login,
                        new gh.Webhook()
                    {
                        Name   = "web",
                        Active = true,
                        Events = RequiredEvents,
                        Config = new gh.WebhookConfiguration()
                        {
                            Url         = $"https://{_apiHostName}/webhook/org/{_orgId}",
                            ContentType = "json",
                            Secret      = newHook.Secret.ToString(),
                        },
                    });

                    if (addHookResponse.Succeeded)
                    {
                        newHook.GitHubId  = addHookResponse.Result.Id;
                        newHook.LastError = null;
                        changes           = await context.BulkUpdateHooks(hooks : new[] { newHook });
                    }
                    else
                    {
                        this.Error($"Failed to add hook for org '{_login}' ({_orgId}): {addHookResponse.Status} {addHookResponse.Error}");
                    }
                } catch (Exception e) {
                    e.Report($"Failed to add hook for org '{_login}' ({_orgId})");
                    // Save LastError
                    await context.BulkUpdateHooks(hooks : new[] { newHook });
                }
            }
            else if (!RequiredEvents.SetEquals(hook.Events.Split(',')))
            {
                var editHook = new HookTableType()
                {
                    Id        = hook.Id,
                    GitHubId  = hook.GitHubId,
                    Secret    = hook.Secret,
                    Events    = hook.Events,
                    LastError = DateTimeOffset.UtcNow, // Default to faulted.
                };

                try {
                    this.Info($"Updating webhook {_login}/{hook.GitHubId} from [{hook.Events}] to [{string.Join(",", RequiredEvents)}]");
                    var editResponse = await admin.EditOrganizationWebhookEvents(_login, (long)hook.GitHubId, RequiredEvents);

                    if (editResponse.Succeeded)
                    {
                        editHook.LastError = null;
                        editHook.GitHubId  = editResponse.Result.Id;
                        editHook.Events    = string.Join(",", editResponse.Result.Events);
                        await context.BulkUpdateHooks(hooks : new[] { editHook });
                    }
                    else if (editResponse.Status == HttpStatusCode.NotFound)
                    {
                        // Our record is out of date.
                        this.Info($"Failed to edit hook for org '{_login}'. Deleting our hook record. {editResponse.Status} {editResponse.Error}");
                        changes = await context.BulkUpdateHooks(deleted : new[] { editHook.Id });
                    }
                    else
                    {
                        throw new Exception($"Failed to edit hook for org '{_login}': {editResponse.Status} {editResponse.Error}");
                    }
                } catch (Exception e) {
                    e.Report();
                    // Save LastError
                    await context.BulkUpdateHooks(hooks : new[] { editHook });
                }
            }

            return(changes);
        }