/// <summary> /// Processes resources that can point to artifacts in S3. /// </summary> /// <param name="resource">The resource.</param> /// <param name="templatePath">The template path.</param> /// <param name="workingDirectory">The working directory.</param> /// <returns><c>true</c> if the containing template should be modified (to point to S3); else <c>false</c></returns> /// <exception cref="InvalidDataException">Unsupported derivative of FileSystemInfo</exception> /// <exception cref="MissingMethodException">Missing constructor for the replacement template artifact.</exception> private async Task <bool> ProcessResource( IResource resource, string templatePath, string workingDirectory) { var templateModified = false; foreach (var propertyToCheck in PackagedResources[resource.Type]) { ResourceUploadSettings resourceToUpload; // See if we have a lambda var lambdaResource = new LambdaArtifact(this.pathResolver, resource, this.logger, this.platform, templatePath); if (lambdaResource.ArtifactType != LambdaArtifactType.NotLambda) { // We do using (var packager = LambdaPackager.CreatePackager(lambdaResource, this.s3Util, this.logger, this.platform)) { resourceToUpload = await packager.Package(workingDirectory); if (resourceToUpload == null) { // Lambda syntax does not imply a template modification // i.e. it is inline code or already an S3 reference. continue; } // The template will be altered to an S3 location, // however the zip may or may not be uploaded. templateModified = true; } } else { string resourceFile; try { resourceFile = (string)resource.GetResourcePropertyValue(propertyToCheck.PropertyPath); } catch (FormatException) { if (!propertyToCheck.Required) { // Property is missing, but CloudFormation does not require it. continue; } throw; } if (resourceFile == null) { // Property was not found, or was not a value type. continue; } var fsi = ResolveFileSystemResource(this.pathResolver, templatePath, resourceFile); if (fsi == null) { // Property value did not resolve to a path in the file system continue; } templateModified = true; switch (fsi) { case FileInfo fi: // Property value points to a file resourceToUpload = await ArtifactPackager.PackageFile( fi, workingDirectory, propertyToCheck.Zip, this.s3Util, this.logger); break; case DirectoryInfo di: // Property value points to a directory, which must always be zipped. resourceToUpload = await ArtifactPackager.PackageDirectory( di, workingDirectory, this.s3Util, this.logger); break; default: // Should never get here, but shuts up a bunch of compiler/R# warnings throw new InvalidDataException( $"Unsupported derivative of FileSystemInfo: {fsi.GetType().FullName}"); } } if (!resourceToUpload.HashMatch) { resourceToUpload.KeyPrefix = this.s3Util.KeyPrefix; resourceToUpload.Metadata = this.s3Util.Metadata; await this.s3Util.UploadResourceToS3Async(resourceToUpload); } if (propertyToCheck.ReplacementType == typeof(string)) { // Insert the URI directly resource.UpdateResourceProperty(propertyToCheck.PropertyPath, resourceToUpload.S3Artifact.Url); } else { // Create an instance of the new mapping // ReSharper disable once StyleCop.SA1305 var s3Location = propertyToCheck.ReplacementType.GetConstructor(new[] { typeof(S3Artifact) }) ?.Invoke(new object[] { resourceToUpload.S3Artifact }); if (s3Location == null) { throw new MissingMethodException(propertyToCheck.ReplacementType.FullName, ".ctor(S3Artifact)"); } // and set on the resource property resource.UpdateResourceProperty(propertyToCheck.PropertyPath, s3Location); } } return(templateModified); }
/// <summary> /// Package the artifact /// </summary> /// <param name="workingDirectory">Working directory to use for packaging</param> /// <returns><see cref="ResourceUploadSettings"/>; else <c>null</c> if nothing to upload (hash sums match)</returns> public async Task <ResourceUploadSettings> Package(string workingDirectory) { this.ValidateHandler(); // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault - Intentionally so, all other cases are effectively the default switch (this.LambdaArtifact.ArtifactType) { case LambdaArtifactType.ZipFile: // Already zipped FileInfo lambdaFile = this.LambdaArtifact; var resourceToUpload = new ResourceUploadSettings { File = lambdaFile, Hash = lambdaFile.MD5() }; await this.S3.ObjectChangedAsync(resourceToUpload); // Template will always be modified, however the resource may not need upload. return(resourceToUpload); case LambdaArtifactType.Inline: case LambdaArtifactType.FromS3: // Template is unchanged if code is inline or already in S3 return(null); default: var dependencies = this.LambdaArtifact.LoadDependencies(); if (!dependencies.Any()) { switch (this.LambdaArtifact.ArtifactType) { case LambdaArtifactType.CodeFile: return(await ArtifactPackager.PackageFile( this.LambdaArtifact, workingDirectory, true, this.S3, this.Logger)); case LambdaArtifactType.Directory: return(await ArtifactPackager.PackageDirectory( this.LambdaArtifact, workingDirectory, this.S3, this.Logger)); } } // If we get here, there are dependencies to process var packageDirectory = this.PreparePackage(workingDirectory); return(await ArtifactPackager.PackageDirectory( new DirectoryInfo(packageDirectory), workingDirectory, this.S3, this.Logger)); } }