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

Allow JT specification and prompting for project branch #4265

Merged

Conversation

AlanCoding
Copy link
Member

SUMMARY

Implements the core of #282

Decisions that are non-obvious from the prior discussion:

  • using the default branch with allow_override being True will behave the same as other projects (offline runs are possible). I still would argue that this isn't consistent, and will be a surprise to the user, but any decision here would have problems with communication.
  • prompting in workflows is allowed in the node prompts, same with schedules
  • playbook list is produced from project's default branch (for use as suggestions)
ISSUE TYPE
  • Feature Pull Request
COMPONENT NAME
  • API
ADDITIONAL INFORMATION

We hope to handle in another PR:

  • organization as field on JT
  • cloning from a PR ref (may be done in Ansible core)

@softwarefactory-project-zuul
Copy link
Contributor

Merge Failed.

This change or one of its cross-repo dependencies was unable to be automatically merged with the current state of its repository. Please rebase the change and upload a new patchset.

@AlanCoding AlanCoding changed the title Branch feature phase 2 Allow JT specification and prompting for project branch Jul 3, 2019
@AlanCoding AlanCoding force-pushed the branch_feature_phase_2 branch from ffe2c31 to 15418a7 Compare July 3, 2019 20:52
@AlanCoding AlanCoding force-pushed the branch_feature_phase_2 branch from 15418a7 to 20867a3 Compare July 8, 2019 12:10
@matburt
Copy link
Member

matburt commented Jul 8, 2019

Can you explain this more?

using the default branch with allow_override being True will behave the same as other projects (offline runs are possible). I still would argue that this isn't consistent, and will be a surprise to the user, but any decision here would have problems with communication.

@AlanCoding
Copy link
Member Author

Using example:

https://github.com/ansible/ansible/branches

Steps:

  • I create a project from the Ansible core repo with
    • allow_override is True
    • default scm_branch is devel
  • Right after creating it
    • I run a job without overriding the branch
    • I run a job that runs with stable-2.8 branch
  • I return a week later and relaunch both of those jobs
    • devel does not pull a newer revision
    • stable-2.8 does pull the latest revision from github and runs that

This is because we run against the revision of the last "check"-type project update, which has its revision stored. Prompted, non-default branches, do not have their revision saved.

We can change that, but a lot of question need to be answered. This behavior both was the least invasive, and closest to the requirements discussed in planning. I wanted to give pushback on this, but I'm happy with it now. While it's surprising that the default branch behaves differently from the non-default branch, it would also be surprising that different values of allow_override would behave differently. So unless we save the revision of all branches (technically problematic in some cases, impossible in others), the current implementation seems like the best option.

I might take some of this and put it in the docs folder, that had slipped my mind so-far.

Copy link
Member

@matburt matburt left a comment

Choose a reason for hiding this comment

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

There's a lot going on in tasks.py here... I might want you to annotate what's going on and what you are working towards here, especially since it's going to affect project syncing overall.

if 'allow_override' in attrs and self.instance:
if attrs['allow_override'] != self.instance:
raise serializers.ValidationError({
'allow_override': _('Branch override behavior of a project cannot be changed after creation.')})
Copy link
Member

Choose a reason for hiding this comment

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

Why? Even if we know why we need to be able to tell people why.

Copy link
Member

Choose a reason for hiding this comment

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

  1. Not sure I like this
  2. dont' show me button if I can't do it
  3. This is not coming up through UI, getting mysterious 400 instead:
    Screenshot from 2019-07-24 14-05-29

Copy link
Member Author

Choose a reason for hiding this comment

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

Let me recap the reason that this was added, say someone does this:

  • make project with allow_override being true
  • makes JT with a non-blank scm_branch
  • changes project allow_override field back to False

Now the user has a JT that specifies a branch that we cannot apply.

Providing an outline of this case is far too much wording to put in an error message, but I agree the UI should at least show the error message from the API. Or hide it when in edit mode.

Also, this is more complicated than the other "prompts" related rejections of fields. If a JT's field is rejected because the project is configured to not allow it, we currently have no mechanism or precedent for dealing with that. We have no way of letting the user know that's broken without a lot of novel UI work.

Copy link
Member Author

Choose a reason for hiding this comment

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

After more discussions, I agree the API side of this can be made smoother for the user interaction.

It would be fairly easy to combine a check with this, something like JobTemplate.objects.filter(project=obj).exclude(scm_branch="").exists(), and then if both this condition is satisfied, and the allow_override value changes from its prior value, then we could give a more specific error message:

Branch override behavior of this project is currently relied on by one or more existing job templates.

Then if there are no such JTs, we can just allow the PATCH/PUT.

Copy link
Contributor

Choose a reason for hiding this comment

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

+1 to @kdelee's view of things and your solution seems to be a step in the right direction @AlanCoding , it seems allowing the change makes more sense for the end user -- it would also make it easier if the dependent templates could be listed, though I'm unsure of performance impact.

Copy link
Member Author

Choose a reason for hiding this comment

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

it would also make it easier if the dependent templates could be listed

That's possible, but depends on how.

If all you're suggesting is a list of names or primary keys in the error message, then sure. That is super easy, and IMO no performance impact that matters. But this will not be linked.

Giving primary keys would be the most concise. If the message is too long, it will overflow the popup box, because error messages are intended to be concise. But if I give primary keys, I worry that someone will say that's a bad UX. But I think handling any special UI cases here is going way overboard for a corner case.

Copy link
Contributor

@dsesami dsesami Jul 25, 2019

Choose a reason for hiding this comment

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

@AlanCoding I see the issue. How about something like a warning that existing job templates use branch override behavior, and offer a confirm button that would revert them if the person really doesn't care? If that's a lot to handle and offers chaos, then just the warning would be good though.

@@ -2701,7 +2706,7 @@ def get_summary_fields(self, obj):
class JobOptionsSerializer(LabelsListMixin, BaseSerializer):

class Meta:
fields = ('*', 'job_type', 'inventory', 'project', 'playbook',
fields = ('*', 'job_type', 'inventory', 'project', 'playbook', 'scm_branch',
Copy link
Member

Choose a reason for hiding this comment

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

This needs to be consistent... it's scm_revision elsewhere, which is more appropriate... it's more than just branch.

Copy link
Member Author

Choose a reason for hiding this comment

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

JobSerializer has both fields scm_branch and scm_revision. The field name scm_branch was borrowed from the Project model.

The field scm_revision is the SHA1 hash or technical specifier of the commit that a job is ran against (like "r49" for subversion). This serves the purpose of audibility in AWX / Tower so that someone can look back in time and be sure they can recover the exact version of the playbook that was ran.

scm_branch is given by a user at some point. It can be several things, and the Ansible git module goes through fallback logic where it checks if it is (1) a branch (2) a tag (3) assume it's a commit, and possibly error. The fallback logic for evaluating an scm_branch here is modeled after their process.

@@ -396,7 +418,7 @@ def _accept_or_ignore_job_kwargs(self, **kwargs):
# Not considered an error for manual launch, to support old
# behavior of putting them in ignored_fields and launching anyway
if 'prompts' not in exclude_errors:
errors_dict[field_name] = _('Field is not configured to prompt on launch.').format(field_name=field_name)
errors_dict[field_name] = _('Field is not configured to prompt on launch.')
Copy link
Member

Choose a reason for hiding this comment

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

🤣

Copy link
Contributor

Choose a reason for hiding this comment

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

Whoops.

@@ -41,6 +43,10 @@
# Django-CRUM
from crum import impersonate

# GitPython
import git
Copy link
Member

Choose a reason for hiding this comment

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

I think I may need some help understanding what is going on in this file.... is it just Runner usage cleanup?

except (ValueError, BadGitName):
pass
else:
if git_repo.head.commit.hexsha == job.project.scm_revision:
Copy link
Member

Choose a reason for hiding this comment

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

Is this to cover the scenario where they gave a specific revision? Rather than a branch or a tag?

project_path = p.get_project_path(check_if_exists=False)
git_repo = git.Repo(project_path)
try:
commit = git_repo.commit(instance.scm_branch)
Copy link
Member

Choose a reason for hiding this comment

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

I assume this works on all ref types like tags and branches?

@@ -2748,16 +2753,28 @@ def to_representation(self, obj):

def validate(self, attrs):
if 'project' in self.fields and 'playbook' in self.fields:
project = attrs.get('project', self.instance and self.instance.project or None)
project = attrs.get('project', self.instance.project if self.instance else None)
Copy link
Contributor

Choose a reason for hiding this comment

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

Yes please.

migrations.AlterField(
model_name='project',
name='scm_update_cache_timeout',
field=models.PositiveIntegerField(blank=True, default=0, help_text='The number of seconds after the last project update ran that a new project update will be launched as a job dependency.'),
Copy link
Contributor

Choose a reason for hiding this comment

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

This only changed because the help_text got an extra space in it?

Copy link
Member Author

@AlanCoding AlanCoding Jul 9, 2019

Choose a reason for hiding this comment

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

Yes, this is correct, two words ran together. I figured this is a good time to fix it because I was already migrating fields on the model.

logger.info('Skipping project sync for {} because commit is locally available'.format(job.log_format))
needs_sync = False # requested commit is already locally available
except (ValueError, BadGitName):
pass
Copy link
Contributor

@ryanpetrello ryanpetrello Jul 8, 2019

Choose a reason for hiding this comment

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

Should we put a logger.error() here so people can see in the logs that they put in an invalid ref?

@AlanCoding AlanCoding force-pushed the branch_feature_phase_2 branch from ce2e4ac to b73e121 Compare July 15, 2019 17:53
@ryanpetrello ryanpetrello requested a review from jbradberry July 16, 2019 13:04
# manual projects are not synced, user has responsibility for that
needs_sync = False
elif not os.path.exists(project_path):
logger.debug('Performing fresh clone of {} on this instance.'.format(job.project))
Copy link
Contributor

Choose a reason for hiding this comment

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

These added logging statements here all seem like they should probably be info-level, not debug.

Copy link
Member Author

Choose a reason for hiding this comment

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

https://github.com/ansible/awx/pull/3882/files#diff-8f5e2fe91e83c971fc84cb35476362db

A lot of the INFO-level logs got axed en masse (almost all, really), as per the reasons there. I raised the level of some select log statements in this PR. For "Skipping project sync", I can buy INFO, because it explains the lack of a unified job. But for statements like this one, they fit in the category of normal cluster operation and that seems to fit in the DEBUG stream precedent.

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree DEBUG is more appropriate here - this is just informational regarding work we're doing, and it's not an abnormal thing to have happen. Raising the log level will probably make the logs pretty noisy.

@AlanCoding AlanCoding force-pushed the branch_feature_phase_2 branch from b73e121 to 1ee1645 Compare July 17, 2019 18:40
@AlanCoding

This comment has been minimized.

@AlanCoding AlanCoding force-pushed the branch_feature_phase_2 branch 2 times, most recently from 052a945 to 2894ade Compare July 18, 2019 18:49
Copy link
Contributor

@jakemcdermott jakemcdermott left a comment

Choose a reason for hiding this comment

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

AlanCoding and others added 13 commits August 12, 2019 22:13
Update migration syntax to Django 2

fix status bug where canceled switched to error
* just like we support ansible-galaxy role install, support
ansible-galaxy collection install
* ansible:devel now has ansible-galaxy collection support
Addresses some cases where
  collection requirements do not exist
  collection requirements cannot be evaluated

Consolidate logic for roles and collection installs
* Similar to roles/requirements.yml sync optimization logic.
bump migration

fine tune validation of project allow_override
  return highly custom error message

Restore branch after syncs to address bugs
  encountered after changing scm_refspec

remove unused code to determine scm_revision

Check Ansible version before project update and
  do not install collections if Ansible version too old

Add docs related to project branch override
  New file specific to branch override and refspec
Complete docs on collections to reflect current
  implementation and give a folder tree example
Update clustering docs related to project syncs

Fix bug where git depth was ignored during the
  local clone from project folder to run folder

Fix bug where submodules were not copied
- add refspec field to project
- update refspec and branch help text on project form
- add refspec field to job detail
- adjust form gen and ProcessErrors to show api errors for checkbox_groups correctly
- consolidate showPromptButton conditionals and fix the add/edit workflow node one for showing prompt when only branch is promptable
Additional API housekeeping, removing unused code

Treat default branch as no branch provided
@AlanCoding AlanCoding force-pushed the branch_feature_phase_2 branch from addeff2 to be21a8b Compare August 13, 2019 02:19
@softwarefactory-project-zuul
Copy link
Contributor

Build succeeded.

Copy link
Member

@matburt matburt left a comment

Choose a reason for hiding this comment

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

This lgtm

@softwarefactory-project-zuul
Copy link
Contributor

Build succeeded (gate pipeline).

@softwarefactory-project-zuul softwarefactory-project-zuul bot merged commit 43d816b into ansible:devel Aug 14, 2019
@AlanCoding AlanCoding deleted the branch_feature_phase_2 branch October 9, 2019 14:47
ryanpetrello pushed a commit to ryanpetrello/awx that referenced this pull request Apr 28, 2020
Restructuring modules so that lookup don't happen if deleting
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants