예제 #1
0
 /// <summary>
 /// Upserts the given <paramref name="recordSet"/>.
 /// </summary>
 public static Task UpsertZoneRecordSetAsync(this IElementEndpoint <Zone> endpoint,
                                             RecordSet recordSet,
                                             CancellationToken cancellationToken = default)
 {
     recordSet.ChangeType = ChangeType.Replace;
     return(endpoint.PatchRecordSetAsync(recordSet, cancellationToken));
 }
예제 #2
0
        /// <summary>
        /// Applies a JSON Patch to the entity. Sends the patch instructions to the server for processing; falls back to local processing with optimistic concurrency if that fails.
        /// </summary>
        /// <typeparam name="TEntity">The type of entity the endpoint represents.</typeparam>
        /// <param name="endpoint">The endpoint representing the entity.</param>
        /// <param name="patchAction">Callback for building a patch document describing the desired modifications.</param>
        /// <param name="maxRetries">The maximum number of retries to perform for optimistic concurrency before giving up.</param>
        /// <param name="cancellationToken">Used to cancel the request.</param>
        /// <returns>The <typeparamref name="TEntity"/> as returned by the server, possibly with additional fields set. <c>null</c> if the server does not respond with a result entity.</returns>
        /// <exception cref="NotSupportedException"><see cref="IEndpoint.Serializer"/> is not a <see cref="JsonMediaTypeFormatter"/>.</exception>
        /// <exception cref="InvalidDataException"><see cref="HttpStatusCode.BadRequest"/></exception>
        /// <exception cref="AuthenticationException"><see cref="HttpStatusCode.Unauthorized"/></exception>
        /// <exception cref="UnauthorizedAccessException"><see cref="HttpStatusCode.Forbidden"/></exception>
        /// <exception cref="KeyNotFoundException"><see cref="HttpStatusCode.NotFound"/> or <see cref="HttpStatusCode.Gone"/></exception>
        /// <exception cref="InvalidOperationException">The number of retries performed for optimistic concurrency exceeded <paramref name="maxRetries"/>.</exception>
        /// <exception cref="HttpRequestException">Other non-success status code.</exception>
        public static async Task <TEntity> UpdateAsync <TEntity>(this IElementEndpoint <TEntity> endpoint, Action <JsonPatchDocument <TEntity> > patchAction, int maxRetries = 3, CancellationToken cancellationToken = default)
            where TEntity : class
        {
            if (!(endpoint.Serializer is JsonMediaTypeFormatter serializer))
            {
                throw new NotSupportedException($"JSON Patch can only be used if the endpoint's serializer is a {nameof(JsonMediaTypeFormatter)}.");
            }

            var patch = new JsonPatchDocument <TEntity>(new List <Operation <TEntity> >(), serializer.SerializerSettings.ContractResolver);

            patchAction(patch);

            var response = await endpoint.HttpClient.SendAsync(new HttpRequestMessage(HttpMethods.Patch, endpoint.Uri)
            {
                Content = new StringContent(JsonConvert.SerializeObject(patch))
                {
                    Headers = { ContentType = new MediaTypeHeaderValue("application/json-patch+json") }
                }
            }, cancellationToken).NoContext();

            if (response.StatusCode == HttpStatusCode.NotFound || response.StatusCode == HttpStatusCode.MethodNotAllowed)
            {
                return(await endpoint.UpdateAsync(patch.ApplyTo, maxRetries, cancellationToken));
            }

            if (!response.IsSuccessStatusCode)
            {
                await endpoint.ErrorHandler.HandleAsync(response).NoContext();
            }

            return(response.Content == null
                ? default
                : await response.Content.ReadAsAsync <TEntity>(endpoint.Serializer, cancellationToken));
        }
예제 #3
0
        /// <summary>
        /// Reads the current state of the entity, applies a change to it and stores the result. Applies optimistic concurrency using automatic retries.
        /// </summary>
        /// <typeparam name="TEntity">The type of entity the endpoint represents.</typeparam>
        /// <param name="endpoint">The endpoint representing the entity.</param>
        /// <param name="updateAction">A callback that takes the current state of the entity and applies the desired modifications.</param>
        /// <param name="maxRetries">The maximum number of retries to perform for optimistic concurrency before giving up.</param>
        /// <param name="cancellationToken">Used to cancel the request.</param>
        /// <returns>The <typeparamref name="TEntity"/> as returned by the server, possibly with additional fields set. <c>null</c> if the server does not respond with a result entity.</returns>
        /// <exception cref="InvalidDataException"><see cref="HttpStatusCode.BadRequest"/></exception>
        /// <exception cref="AuthenticationException"><see cref="HttpStatusCode.Unauthorized"/></exception>
        /// <exception cref="UnauthorizedAccessException"><see cref="HttpStatusCode.Forbidden"/></exception>
        /// <exception cref="KeyNotFoundException"><see cref="HttpStatusCode.NotFound"/> or <see cref="HttpStatusCode.Gone"/></exception>
        /// <exception cref="InvalidOperationException">The number of retries performed for optimistic concurrency exceeded <paramref name="maxRetries"/>.</exception>
        /// <exception cref="HttpRequestException">Other non-success status code.</exception>
        public static async Task <TEntity> UpdateAsync <TEntity>(this IElementEndpoint <TEntity> endpoint, Action <TEntity> updateAction, int maxRetries = 3, CancellationToken cancellationToken = default)
        {
            int retryCounter = 0;

            while (true)
            {
                var entity = await endpoint.ReadAsync(cancellationToken);

                cancellationToken.ThrowIfCancellationRequested();

                updateAction(entity);
                cancellationToken.ThrowIfCancellationRequested();

                try
                {
                    return(await endpoint.SetAsync(entity, cancellationToken));
                }
                catch (InvalidOperationException)
                {
                    if (retryCounter++ >= maxRetries)
                    {
                        throw;
                    }
                    cancellationToken.ThrowIfCancellationRequested();
                }
            }
        }
예제 #4
0
        private static async Task UpdateRecord(
            AutoSpf.FlattenedInclude flattenedInclude,
            Config config,
            Zone zone,
            IElementEndpoint <Zone> zoneEndpoint
            )
        {
            var name     = flattenedInclude.DName.Replace(config.AutoSpfDomain, config.DestinationZone);
            var newValue = flattenedInclude.Value.Replace(config.AutoSpfDomain, config.DestinationZone);

            if (!newValue.EndsWith("\""))
            {
                newValue += "\"";
            }

            if (!newValue.StartsWith("\""))
            {
                newValue = "\"" + newValue;
            }

            var existingRecord = zone.RecordSets.FirstOrDefault(x => x.Name.Equals(name));

            if (existingRecord != null)
            {
                var needsUpdate =
                    existingRecord.Records.Count != 1 ||
                    existingRecord.Records[0].Content != newValue;
                if (needsUpdate)
                {
                    Console.WriteLine($"Updating {name}");
                    Console.WriteLine($"{newValue}");

                    existingRecord.ChangeType = ChangeType.Replace;
                    existingRecord.Records.Clear();
                    existingRecord.Records.Add(new Record(newValue));
                    await zoneEndpoint.PatchRecordSetAsync(existingRecord);

                    Console.WriteLine("Done.");
                    Console.WriteLine();
                }
                else
                {
                    Console.WriteLine($"{name} is already up-to-date");
                }
            }
            else
            {
                Console.WriteLine($"Creating {name}");
                Console.WriteLine($"{newValue}");

                var record = new RecordSet
                {
                    ChangeType = ChangeType.Replace,
                    Name       = name,
                    Records    = new List <Record> {
                        new(newValue)
                    },
예제 #5
0
        /// <summary>
        /// Gets a specific <see cref="RecordSet"/> identified by a <paramref name="recordSetName"/>.
        /// </summary>
        /// <exception cref="RecordSetNotFoundException"></exception>
        public static async Task <RecordSet> GetRecordSetAsync(this IElementEndpoint <Zone> endpoint,
                                                               CanonicalName recordSetName,
                                                               CancellationToken cancellationToken = default)
        {
            var zone = await endpoint.ReadAsync(cancellationToken);

            var recordSet = zone.RecordSets.FirstOrDefault(rrs => rrs.Name.Equals(recordSetName));

            if (recordSet == null)
            {
                throw new RecordSetNotFoundException(recordSetName);
            }

            return(recordSet);
        }
예제 #6
0
        /// <summary>
        /// Modifies existing <see cref="RecordSet"/>s.
        /// </summary>
        /// <remarks><see cref="RecordSet.ChangeType"/> must be set accordingly.</remarks>
        /// <exception cref="ArgumentNullException">If <see cref="RecordSet.ChangeType"/> is not set.</exception>
        /// <exception cref="InvalidDataException"><see cref="HttpStatusCode.BadRequest" /></exception>
        /// <exception cref="AuthenticationException"><see cref="HttpStatusCode.Unauthorized" /></exception>
        /// <exception cref="UnauthorizedAccessException"><see cref="HttpStatusCode.Forbidden" /></exception>
        /// <exception cref="KeyNotFoundException"><see cref="HttpStatusCode.NotFound" /> or <see cref="HttpStatusCode.Gone" /></exception>
        /// <exception cref="HttpRequestException">Other non-success status code.</exception>
        public static Task PatchRecordSetAsync(this IElementEndpoint <Zone> endpoint,
                                               RecordSet recordSet,
                                               CancellationToken cancellationToken = default)
        {
            if (!recordSet.ChangeType.HasValue)
            {
                throw new ArgumentException($"{nameof(ChangeType)} must be set.", nameof(recordSet));
            }

            string zoneName = endpoint.Uri.AbsolutePath.Split('/').Last();

            return(endpoint.MergeAsync(new Zone(zoneName)
            {
                RecordSets = { recordSet }
            }, cancellationToken));
        }
 public ZoneEndpointExtensionsFacts()
 {
     _endpoint = _endpointMock.Object;
 }
예제 #8
0
 public override void SetUp()
 {
     base.SetUp();
     _endpoint = new ElementEndpoint<MockEntity>(EntryEndpoint, "endpoint");
 }
예제 #9
0
 public ElementEndpointTest()
 {
     _endpoint = new ElementEndpoint <MockEntity>(EntryEndpoint, "endpoint");
 }
예제 #10
0
 /// <summary>
 /// Creates a new REST element command.
 /// </summary>
 /// <param name="endpoint">The endpoint this command operates on.</param>
 public ElementCommand(IElementEndpoint <TEntity> endpoint)
     : base(endpoint)
 {
 }