Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(assets): support exceptions to exclude patterns #4473

Merged
merged 44 commits into from
Oct 30, 2019
Merged

Conversation

nmussy
Copy link
Contributor

@nmussy nmussy commented Oct 11, 2019

Allows adding back files and directories from a previously excluded directory. Previous implementations of copyDirectory and fingerprint stopped walking through the directory if it was ignored (see comment in #4450)

  • expand dir exclusions into dir/*, to allow minimist to match files in folder
  • add listFilesRecursively method, removing duped code from copy and fingerprint
  • add additional .dockerignore test cases into @aws-cdk/aws-ecr-assets
  • refactor existing test cases

Fixes #4450


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license

@mergify
Copy link
Contributor

mergify bot commented Oct 11, 2019

Thanks so much for taking the time to contribute to the AWS CDK ❤️

We will shortly assign someone to review this pull request and help get it
merged. In the meantime, please take a minute to make sure you follow this
checklist
:

  • PR title type(scope): text
    • type: fix, feat, refactor go into CHANGELOG, chore is hidden
    • scope: name of module without aws- or cdk- prefix or postfix (e.g. s3 instead of aws-s3-deployment)
    • text: use all lower-case, do not end with a period, do not include issue refs
  • PR Description
    • Rationale: describe rationale of change and approach taken
    • Issues: indicate issues fixed via: fixes #xxx or closes #xxx
    • Breaking?: last paragraph: BREAKING CHANGE: <describe what changed + link for details>
  • Testing
    • Unit test added. Prefer to add a new test rather than modify existing tests
    • CLI or init templates change? Re-run/add CLI integration tests
  • Documentation
    • README: update module README to describe new features
    • API docs: public APIs must be documented. Copy from official AWS docs when possible
    • Design: for significant features, follow design process

@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • Result: SUCCEEDED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • Result: FAILED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • Result: FAILED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

@nmussy nmussy marked this pull request as ready for review October 11, 2019 19:50
@nmussy nmussy requested a review from rix0rrr as a code owner October 11, 2019 19:50
@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • Result: FAILED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

packages/@aws-cdk/assets/lib/fs/utils.ts Outdated Show resolved Hide resolved
if (stat.isDirectory()) {
// to help future shouldExclude calls, we're changing the exlusion patterns
// by expliciting "dir" exclusions to "dir/*" (same with "!dir" -> "!dir/*")
exclude = exclude.reduce<string[]>((res, pattern) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this should be done in-line while traversing. It is also possible to run through the list of entries in the ignore list, see which ones are directories and treat those as prefixes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, I'd like to convert the "list of exclude/include entries" into a function or class that encapsulates the behavior as soon as possible and pass that around.

I'm thinking something like:

const ignoreList = parseIgnoreList(rootDirectory, ...list of strings);

ignoreList will be either of type (path: string) => boolean, or be a class with a method of that type.

I would have that as an argument to listFilesRecursively().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue is that it wouldn't be very economical, fs calls-wise. I'm filling the exclude directory on the fly because I'm also checking the stat of the files I'm traversing.

@@ -0,0 +1,22 @@
# This a comment, followed by an empty line
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do these tests in memory?

I'm thinking of a test where we have preloaded this list of ignore patterns, and pushing a number of paths past it (either by means of shouldExclude() or by means of a new IgnoreList API)

That is effectively the same and easier to change/assert against than making a whole new fixture with "just these" files in it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ended up creating a createFsStructureFromTree function, and removing the previous fixtures (with the exception of demo-image/).

Does that work for you?

packages/@aws-cdk/assets/lib/fs/utils.ts Outdated Show resolved Hide resolved
packages/@aws-cdk/assets/lib/fs/copy.ts Outdated Show resolved Hide resolved
packages/@aws-cdk/assets/lib/fs/utils.ts Outdated Show resolved Hide resolved
@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • Result: FAILED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • Result: FAILED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • Result: SUCCEEDED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

@@ -0,0 +1,98 @@
import fs = require('fs');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure whether it's necessary to upgrade this to a feature of the assert library yet, especially since it doesn't really assert anything, will not be used by most of the construct libraries, and will never be JSII-able.

At least we can just keep this as a testing utility in the assets library until we definitely need it somewhere else, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved it to assert because I was using it both in assets and aws-ecr-assets. I could move it to assets instead, and expose it to make to available to aws-ecr-assets?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Yes, I think put it in assets, and in fact you could import it directly from the test/ directory in aws-ecr-assets:

import fsUtils = require('@aws-cdk/assets/test/fs-utils');

It's not super nice, but I don't want test helpers to be part of the public API of a package. This usage would be okay by me since it's between 1st-party packages only. Would be nice to spend a couple of comment lines on it when you're making that change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

* @returns `true` if the file should be excluded, followed by the index of the rule applied
*/
export function shouldExcludePriority(exclude: string[], filePath: string): [boolean, number] {
return exclude.reduce<[boolean, number]>((res, pattern, patternIndex) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know reduce is sexy to use, but since you're not even using current accumulation res in your reducer, I don't really see the value. What's wrong with a simple for loop that returns the last set value?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was originally using a map to have both pattern and patternIndex. I'll revert back to it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

*
* @returns `true` if the file should be excluded, followed by the index of the rule applied
*/
export function shouldExcludePriority(exclude: string[], filePath: string): [boolean, number] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this function really need to be exported?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name confuses me. Proposed name change: how about evaluateExcludePatterns.

Or in fact, I've been saying this a couple of times already: please put these related functions that operate on the same data structure in a class. They're not as independent as you would like.

class ExcludeRules {
  constructor(private readonly patterns: string[]) { }

  public excludeFile(filePath: string): boolean { ... }
  public excludeDirectory(dirPath: string): boolean { ... }
  
  private evaluate(pathName: string): [boolean, number] { ... }

}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've switched to a class, that I'm still exporting to be able to test it. Same for your evaluate/my evaluateFile, it needs to be public.

* @param exclude exclusion patterns
* @param filePath file path to be assessed against the pattern
*/
export function shouldExcludeDeep(exclude: string[], relativePath: string): boolean {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this function really need to be exported?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was exporting it to test it. It's the same now with the public method

* @param exclude exclusion patterns
* @param directoryPath directory path to be assessed against the pattern
*/
export function shouldExcludeDirectory(exclude: string[], directoryPath: string): boolean {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this function really need to be exported?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was exporting it to test it. It's the same now with the public method

export function shouldExcludeDeep(exclude: string[], relativePath: string): boolean {
const [_shouldExclude] = relativePath.split(path.sep).reduce<[boolean, number, string]>(
([accExclude, accPriority, pathIterator], pathComponent) => {
pathIterator = path.join(pathIterator, pathComponent);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel it would be cleaner if you made a function like pathComponents() (which given a path like a/b/c would return [a, a/b, a/b/c]) and iterated over that.

Again I feel a simple for loop would be more natural. If you really want to make it functional, why not something like:

const resultsAndPrios = pathComponents(relativePath).map(pc => shouldExcludePriority(rules,  pc));
const [shouldExclude] = pickHighest(resultsAndPrios, x => x[1]);
return shouldExclude;

Requires you to write some helpers, but to me is a lot clearer what is happening.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did some refactoring, preferring for loops over reduces. I've only kept maps when I needed to know both the current array value and index.


/**
* Determines whether a given directory should be excluded and not explored further
* This might be true even if the directory is explicitly excluded,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the case you're describing, shouldn't the function return false rather than `true?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

test.done();
},
'contridactory'(test: Test) {
testShouldExcludeDeep(test, ['foo.txt', '!foo.txt'], [], ['foo.txt']);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about contradictory with the order reversed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -192,4 +194,329 @@ export = {
},
}
},

shouldExcludeDeep: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for a thorough test suite!

@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • Result: SUCCEEDED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • Result: SUCCEEDED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

@rix0rrr rix0rrr changed the title fix(assets): support exceptions in exclude patterns fix(assets): support exceptions to exclude patterns Oct 30, 2019
@mergify
Copy link
Contributor

mergify bot commented Oct 30, 2019

Thank you for contributing! Your pull request is now being automatically merged.

@nmussy
Copy link
Contributor Author

nmussy commented Oct 30, 2019

Thanks again @rix0rrr 🎉

@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • Result: SUCCEEDED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • Result: SUCCEEDED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

@mergify mergify bot merged commit b7b4336 into aws:master Oct 30, 2019
@nmussy nmussy deleted the 4450 branch October 30, 2019 11:46
azatoth pushed a commit to azatoth/aws-cdk that referenced this pull request Oct 30, 2019
* chore: better test

* fix: folder exclusion
* additional test cases
* refactor existing tests

* chore: add new test files

* fix: listFilesRecursively refactor (wip)

* fix: finish refactoring listFilesRecursively

* fix: implement mkdirpSync

* fix: symlink discovery

* fix: don't follow symlinks early

* fix: create empty directories

* chore: remove useless let

* fix: symlink fingerprinting

* fix: don't throw when fingerprinting directories

* chore: remove unneeded 'exclude' cloning

* chore: refactor mkdirp calls

* chore: refactor AssetFile

* chore: refactor recursion

* chore: prevent unneeded stats call

* chore: createFsStructureFromTree, remove empty files

* chore: cleanup

* fix: empty-directory test

* feat: shouldExcludeDeep

* chore: fromTree in @/assert, cleanup fn, test

* feat: shouldExcludeDirectory

* chore: refactor listFiles with new methods (missing symlinks)

* feat: add symlink support to fromTree

* fix: fromTree external directory symlink

* fix: listFiles symlink support

* chore: additional contridactory test

* chore: fix docs

* chore: ExcludeRules class refactor

* chore: evaluateFile refactor

* chore: further evaluateFile refactor

* chore: evaluateDirectory refactor

* chore: move FsUtils to assets

* chore: move FsUtils to assets (unstaged files)
shivlaks added a commit that referenced this pull request Nov 14, 2019
shivlaks added a commit that referenced this pull request Nov 14, 2019
…5022)

This reverts commit b7b4336.

This is being reverted as it introduced a regression in assets that contain symlinks during cdk synth.
Fixes #4978
arhea pushed a commit to arhea/aws-cdk that referenced this pull request Nov 14, 2019
…nto 4295-windows-ecs-support

* '4295-windows-ecs-support' of github.com:arhea/aws-cdk:
  chore(deps-dev): bump @types/lodash from 4.14.146 to 4.14.147 (aws#5021)
  Revert "fix(assets): support exceptions to exclude patterns (aws#4473)" (aws#5022)
  chore(deps): bump jsii-pacmak from 0.20.3 to 0.20.5 (aws#5003)
  chore(deps): bump codemaker from 0.20.3 to 0.20.5 (aws#5007)
  chore(deps-dev): bump @types/jest from 24.0.22 to 24.0.23 (aws#4993)
  chore(deps): bump jsii from 0.20.3 to 0.20.5 (aws#5006)
  chore(deps-dev): bump jsii-diff from 0.20.3 to 0.20.5 (aws#5005)
  chore(deps): bump jsii-spec from 0.20.3 to 0.20.5 (aws#5008)
  chore(core): resolve tokens before publishing tree.json (aws#4984)
  feat(cli): adding new option to `cdk deploy` to indicate whether ChangeSet should be executed (aws#4852)
  chore: move semantic.yaml to .github/
  fix(core): unable to find stack by name using the cli in legacy mode (aws#4998)
  fix(ecs-patterns): Fix issue related to protocol being passed to target group (aws#4988)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ecr-assets: incomplete .dockerignore support
3 participants