diff --git a/src/Squirrel/Sources/GitlabSource.cs b/src/Squirrel/Sources/GitlabSource.cs
new file mode 100644
index 000000000..b3214d42d
--- /dev/null
+++ b/src/Squirrel/Sources/GitlabSource.cs
@@ -0,0 +1,233 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Threading.Tasks;
+using Squirrel.Json;
+
+namespace Squirrel.Sources
+{
+ ///
+ /// Describes a Gitlab release, plus any assets that are attached.
+ ///
+ [DataContract]
+ public class GitlabRelease
+ {
+ ///
+ /// The name of the release.
+ ///
+ [DataMember(Name = "name")]
+ public string Name { get; set; }
+
+ ///
+ /// True if this is intended for an upcoming release.
+ ///
+ [DataMember(Name = "upcoming_release")]
+ public bool UpcomingRelease { get; set; }
+
+ ///
+ /// The date which this release was published publically.
+ ///
+ [DataMember(Name = "released_at")]
+ public DateTime ReleasedAt { get; set; }
+
+ ///
+ /// A container for the assets (files) uploaded to this release.
+ ///
+ [DataMember(Name = "assets")]
+ public GitlabReleaseAsset Assets { get; set; }
+ }
+
+ ///
+ /// Describes a container for the assets attached to a release.
+ ///
+ [DataContract]
+ public class GitlabReleaseAsset
+ {
+ ///
+ /// The amount of assets linked to the release.
+ ///
+ [DataMember(Name = "count")]
+ public int Count { get; set; }
+
+ ///
+ /// A list of asset (file) links.
+ ///
+ [DataMember(Name = "links")]
+ public GitlabReleaseLink[] Links { get; set; }
+ }
+
+ ///
+ /// Describes a container for the links of assets attached to a release.
+ ///
+ [DataContract]
+ public class GitlabReleaseLink
+ {
+ ///
+ /// Name of the asset (file) linked.
+ ///
+ [DataMember(Name = "name")]
+ public string Name { get; set; }
+
+ ///
+ /// The url for the asset. This make use of the Gitlab API.
+ ///
+ [DataMember(Name = "url")]
+ public string Url { get; set; }
+
+ ///
+ /// A direct url to the asset, via a traditional URl.
+ /// As a posed to using the API.
+ /// This links directly to the raw asset (file).
+ ///
+ [DataMember(Name = "direct_asset_url")]
+ public string DirectAssetUrl { get; set; }
+
+ ///
+ /// The category type that the asset is listed under.
+ /// Options: 'Package', 'Image', 'Runbook', 'Other'
+ ///
+ [DataMember(Name = "link_type")]
+ public string Type { get; set; }
+ }
+
+ ///
+ /// Retrieves available releases from a GitLab repository. This class only
+ /// downloads assets from the very latest GitLab release.
+ ///
+ public class GitlabSource : IUpdateSource
+ {
+ ///
+ /// The URL of the GitLab repository to download releases from
+ /// (e.g. https://gitlab.com/api/v4/projects/ProjectId)
+ ///
+ public virtual Uri RepoUri { get; }
+
+ ///
+ /// If true, the latest upcoming release will be downloaded. If false, the latest
+ /// stable release will be downloaded.
+ ///
+ public virtual bool UpcomingRelease { get; }
+
+ ///
+ /// The file downloader used to perform HTTP requests.
+ ///
+ public virtual IFileDownloader Downloader { get; }
+
+ ///
+ /// The GitLab release which this class should download assets from when
+ /// executing . This property can be set
+ /// explicitly, otherwise it will also be set automatically when executing
+ /// .
+ ///
+ public virtual GitlabRelease Release { get; set; }
+
+ ///
+ /// The GitLab access token to use with the request to download releases.
+ ///
+ protected virtual string AccessToken { get; }
+
+ ///
+ /// The Bearer token used in the request.
+ ///
+ protected virtual string Authorization => string.IsNullOrWhiteSpace(AccessToken) ? null : "Bearer " + AccessToken;
+
+ ///
+ ///
+ /// The URL of the GitLab repository to download releases from
+ /// (e.g. https://gitlab.com/api/v4/projects/ProjectId)
+ ///
+ ///
+ /// The GitLab access token to use with the request to download releases.
+ ///
+ ///
+ /// If true, the latest upcoming release will be downloaded. If false, the latest
+ /// stable release will be downloaded.
+ ///
+ ///
+ /// The file downloader used to perform HTTP requests.
+ ///
+ public GitlabSource(string repoUrl, string accessToken, bool upcomingRelease, IFileDownloader downloader = null)
+ {
+ RepoUri = new Uri(repoUrl);
+ AccessToken = accessToken;
+ UpcomingRelease = upcomingRelease;
+ Downloader = downloader ?? Utility.CreateDefaultDownloader();
+ }
+
+ ///
+ public Task DownloadReleaseEntry(ReleaseEntry releaseEntry, string localFile, Action progress)
+ {
+ if (Release == null) {
+ throw new InvalidOperationException("No GitLab Release specified. Call GetReleaseFeed or set " +
+ "GitLabSource.Release before calling this function.");
+ }
+
+ var assetUrl = GetAssetUrlFromName(Release, releaseEntry.Filename);
+ return Downloader.DownloadFile(assetUrl, localFile, progress, Authorization, "application/octet-stream");
+ }
+
+ ///
+ public async Task GetReleaseFeed(Guid? stagingId = null, ReleaseEntry latestLocalRelease = null)
+ {
+ var releases = await GetReleases(UpcomingRelease).ConfigureAwait(false);
+ if (releases == null || releases.Count() == 0)
+ throw new Exception($"No Gitlab releases found at '{RepoUri}'.");
+
+ // CS: we 'cache' the release here, so subsequent calls to DownloadReleaseEntry
+ // will download assets from the same release in which we returned ReleaseEntry's
+ // from. A better architecture would be to return an array of "GitlabReleaseEntry"
+ // containing a reference to the GitlabReleaseAsset instead.
+ Release = releases.First();
+
+ var assetUrl = GetAssetUrlFromName(Release, "RELEASES");
+ var releaseBytes = await Downloader.DownloadBytes(assetUrl, Authorization, "application/octet-stream").ConfigureAwait(false);
+ var txt = Utility.RemoveByteOrderMarkerIfPresent(releaseBytes);
+ return ReleaseEntry.ParseReleaseFileAndApplyStaging(txt, stagingId).ToArray();
+ }
+
+ ///
+ /// Given a and an asset filename (eg. 'RELEASES') this
+ /// function will return either or
+ /// , depending whether an access token is available
+ /// or not. Throws if the specified release has no matching assets.
+ ///
+ protected virtual string GetAssetUrlFromName(GitlabRelease release, string assetName)
+ {
+ if (release.Assets == null || release.Assets.Count == 0)
+ {
+ throw new ArgumentException($"No assets found in Gitlab Release '{release.Name}'.");
+ }
+
+ GitlabReleaseLink packageFile =
+ release.Assets.Links.FirstOrDefault(a => a.Name.Equals(assetName, StringComparison.InvariantCultureIgnoreCase));
+ if (packageFile == null)
+ {
+ throw new ArgumentException($"Could not find asset called '{assetName}' in GitLab Release '{release.Name}'.");
+ }
+
+ if (String.IsNullOrWhiteSpace(AccessToken))
+ {
+ return packageFile.DirectAssetUrl;
+ }
+ else
+ {
+ return packageFile.Url;
+ }
+ }
+
+ ///
+ /// Retrieves a list of from the current repository.
+ ///
+ public virtual async Task GetReleases(bool includePrereleases, int perPage = 30, int page = 1)
+ {
+ // https://docs.gitlab.com/ee/api/releases/
+ var releasesPath = $"{RepoUri.AbsolutePath}/releases?per_page={perPage}&page={page}";
+ var baseUri = new Uri("https://gitlab.com");
+ var getReleasesUri = new Uri(baseUri, releasesPath);
+ var response = await Downloader.DownloadString(getReleasesUri.ToString(), Authorization).ConfigureAwait(false);
+ var releases = SimpleJson.DeserializeObject>(response);
+ return releases.OrderByDescending(d => d.ReleasedAt).Where(x => includePrereleases || !x.UpcomingRelease).ToArray();
+ }
+ }
+}