private async Task <bool> ExecuteAsync() { try { if (CommitSha.Length < ShaUsableLength) { Log.LogError($"The CommitSHA should be at least {ShaUsableLength} characters long: CommitSha is '{CommitSha}'. Aborting feed creation."); return(false); } JsonSerializerSettings _serializerSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver(), NullValueHandling = NullValueHandling.Ignore }; // GitHub repos may appear in the repository name with an 'org/repo' form. // When creating a repo, Github aslready replaces all of the characters invalid in AzDO feed names (see below) // with '-' in the repo name. We just need to replace '/' with '-' to deal with the org/repo input. // From the AzDO docs: // The feed name can't contain spaces, start with a '.' or '_', end with a '.', // or contain any of these: @ ~ ; { } ' + = , < > | / \ ? : & $ * " # [ ] % string feedCompatibleRepositoryName = RepositoryName.Replace('/', '-'); string accessType = IsInternal ? "internal" : "public"; string publicSegment = IsInternal ? string.Empty : "public/"; string accessId = IsInternal ? "int" : "pub"; string extraContentInfo = !string.IsNullOrEmpty(ContentIdentifier) ? $"-{ContentIdentifier}" : ""; string baseFeedName = $"darc-{accessId}{extraContentInfo}-{feedCompatibleRepositoryName}-{CommitSha.Substring(0, ShaUsableLength)}"; string versionedFeedName = baseFeedName; bool needsUniqueName = false; int subVersion = 0; Log.LogMessage(MessageImportance.High, $"Creating the new {accessType} Azure DevOps artifacts feed '{baseFeedName}'..."); if (baseFeedName.Length > MaxLengthForAzDoFeedNames) { Log.LogError($"The name of the new feed ({baseFeedName}) exceeds the maximum feed name size of 64 chars. Aborting feed creation."); return(false); } do { using (HttpClient client = new HttpClient(new HttpClientHandler { CheckCertificateRevocationList = true }) { BaseAddress = new Uri(AzureDevOpsFeedsBaseUrl) }) { client.DefaultRequestHeaders.Add( "Accept", $"application/json;api-version={AzureDevOpsFeedsApiVersion}"); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue( "Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes(string.Format("{0}:{1}", "", AzureDevOpsPersonalAccessToken)))); AzureDevOpsArtifactFeed newFeed = new AzureDevOpsArtifactFeed(versionedFeedName); string body = JsonConvert.SerializeObject(newFeed, _serializerSettings); HttpRequestMessage postMessage = new HttpRequestMessage(HttpMethod.Post, $"{publicSegment}_apis/packaging/feeds"); postMessage.Content = new StringContent(body, Encoding.UTF8, "application/json"); HttpResponseMessage response = await client.SendAsync(postMessage); if (response.StatusCode == HttpStatusCode.Created) { needsUniqueName = false; baseFeedName = versionedFeedName; } else if (response.StatusCode == HttpStatusCode.Conflict) { versionedFeedName = $"{baseFeedName}-{++subVersion}"; needsUniqueName = true; if (versionedFeedName.Length > MaxLengthForAzDoFeedNames) { Log.LogError($"The name of the new feed ({baseFeedName}) exceeds the maximum feed name size of 64 chars. Aborting feed creation."); return(false); } } else { throw new Exception($"Feed '{baseFeedName}' was not created. Request failed with status code {response.StatusCode}. Exception: {await response.Content.ReadAsStringAsync()}"); } } } while (needsUniqueName); TargetFeedURL = $"https://pkgs.dev.azure.com/{AzureDevOpsOrg}/{publicSegment}_packaging/{baseFeedName}/nuget/v3/index.json"; TargetFeedName = baseFeedName; Log.LogMessage(MessageImportance.High, $"Feed '{TargetFeedURL}' created successfully!"); } catch (Exception e) { Log.LogErrorFromException(e, true); } return(!Log.HasLoggedErrors); }
private async Task <bool> ExecuteAsync() { try { if (CommitSha?.Length < ShaUsableLength) { Log.LogError($"The CommitSHA should be at least {ShaUsableLength} characters long: CommitSha is '{CommitSha}'. Aborting feed creation."); return(false); } JsonSerializerSettings _serializerSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver(), NullValueHandling = NullValueHandling.Ignore }; // GitHub repos may appear in the repository name with an 'org/repo' form. // When creating a repo, Github aslready replaces all of the characters invalid in AzDO feed names (see below) // with '-' in the repo name. We just need to replace '/' with '-' to deal with the org/repo input. // From the AzDO docs: // The feed name can't contain spaces, start with a '.' or '_', end with a '.', // or contain any of these: @ ~ ; { } ' + = , < > | / \ ? : & $ * " # [ ] % string feedCompatibleRepositoryName = RepositoryName?.Replace('/', '-'); // For clarity, and compatibility with existing infrastructure, we include the feed visibility tag. // This serves two purposes: // 1. In nuget.config files (and elsewhere), the name at a glance can identify its visibility // 2. Existing automation has knowledge of "darc-int" and "darc-pub" for purposes of injecting authentication for internal builds // and managing the isolated feeds within the NuGet.config files. string extraContentInfo = !string.IsNullOrEmpty(ContentIdentifier) ? $"-{ContentIdentifier}" : ""; string baseFeedName = FeedName ?? $"darc-{GetFeedVisibilityTag(AzureDevOpsOrg, AzureDevOpsProject)}{extraContentInfo}-{feedCompatibleRepositoryName}-{CommitSha.Substring(0, ShaUsableLength)}"; string versionedFeedName = baseFeedName; bool needsUniqueName = false; int subVersion = 0; Log.LogMessage(MessageImportance.High, $"Creating the new Azure DevOps artifacts feed '{baseFeedName}'..."); if (baseFeedName.Length > MaxLengthForAzDoFeedNames) { Log.LogError($"The name of the new feed ({baseFeedName}) exceeds the maximum feed name size of 64 chars. Aborting feed creation."); return(false); } string azureDevOpsFeedsBaseUrl = $"https://feeds.dev.azure.com/{AzureDevOpsOrg}/"; do { using (HttpClient client = new HttpClient(new HttpClientHandler { CheckCertificateRevocationList = true }) { BaseAddress = new Uri(azureDevOpsFeedsBaseUrl) }) { client.DefaultRequestHeaders.Add( "Accept", $"application/json;api-version={AzureDevOpsFeedsApiVersion}"); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue( "Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes(string.Format("{0}:{1}", "", AzureDevOpsPersonalAccessToken)))); AzureDevOpsArtifactFeed newFeed = new AzureDevOpsArtifactFeed(versionedFeedName, AzureDevOpsOrg, AzureDevOpsProject); string createBody = JsonConvert.SerializeObject(newFeed, _serializerSettings); using HttpRequestMessage createFeedMessage = new HttpRequestMessage(HttpMethod.Post, $"{AzureDevOpsProject}/_apis/packaging/feeds"); createFeedMessage.Content = new StringContent(createBody, Encoding.UTF8, "application/json"); using HttpResponseMessage createFeedResponse = await client.SendAsync(createFeedMessage); if (createFeedResponse.StatusCode == HttpStatusCode.Created) { needsUniqueName = false; baseFeedName = versionedFeedName; /// This is where we would potentially update the Local feed view with permissions to the organization's /// valid users. But, see <seealso cref="AzureDevOpsArtifactFeed"/> for more info on why this is not /// done this way. } else if (createFeedResponse.StatusCode == HttpStatusCode.Conflict) { versionedFeedName = $"{baseFeedName}-{++subVersion}"; needsUniqueName = true; if (versionedFeedName.Length > MaxLengthForAzDoFeedNames) { Log.LogError($"The name of the new feed ({baseFeedName}) exceeds the maximum feed name size of 64 chars. Aborting feed creation."); return(false); } } else { throw new Exception($"Feed '{baseFeedName}' was not created. Request failed with status code {createFeedResponse.StatusCode}. Exception: {await createFeedResponse.Content.ReadAsStringAsync()}"); } } } while (needsUniqueName); TargetFeedURL = $"https://pkgs.dev.azure.com/{AzureDevOpsOrg}/{AzureDevOpsProject}/_packaging/{baseFeedName}/nuget/v3/index.json"; TargetFeedName = baseFeedName; Log.LogMessage(MessageImportance.High, $"Feed '{TargetFeedURL}' created successfully!"); } catch (Exception e) { Log.LogErrorFromException(e, true); } return(!Log.HasLoggedErrors); }