Merge branch 'kubeflow:main' into issue591_ilya
This commit is contained in:
commit
8292ba1f4a
|
@ -1,4 +1,12 @@
|
|||
blank_issues_enabled: true
|
||||
|
||||
issue_templates:
|
||||
- bug_report.yml
|
||||
- feature_request.yml
|
||||
- planning_epic.yml
|
||||
- planning_feature.yml
|
||||
- planning_task.yml
|
||||
|
||||
contact_links:
|
||||
- name: Kubeflow Documentation
|
||||
url: https://www.kubeflow.org/
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
name: "[Planning] Epic"
|
||||
description: "🛑 Only intended to be used by Kubeflow project managers"
|
||||
title: "[EPIC] <short description>"
|
||||
labels:
|
||||
- "kind/plan-epic"
|
||||
assignees: []
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to create an epic!
|
||||
- type: checkboxes
|
||||
id: certification
|
||||
attributes:
|
||||
label: Certification
|
||||
description: Please confirm your role
|
||||
options:
|
||||
- label: I certify I am an Epic Owner for Kubeflow Notebooks 2.0 and expected to create planning-related issues.
|
||||
required: true
|
||||
- type: textarea
|
||||
id: user-story
|
||||
attributes:
|
||||
label: User Story
|
||||
description: Describe the feature from the end user's perspective, including who they are, what they want to achieve, and why it's important.
|
||||
placeholder: As a [type of user], I want [goal] so that [benefit]
|
||||
validations:
|
||||
required: true
|
|
@ -0,0 +1,48 @@
|
|||
name: "[Planning] Feature"
|
||||
description: "🛑 Only intended to be used by Kubeflow project managers"
|
||||
title: "[FEATURE] <short description>"
|
||||
labels:
|
||||
- "kind/plan-feature"
|
||||
assignees: []
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to create a feature request!
|
||||
- type: checkboxes
|
||||
id: certification
|
||||
attributes:
|
||||
label: Certification
|
||||
description: Please confirm your role
|
||||
options:
|
||||
- label: I certify I am an Epic Owner for Kubeflow Notebooks 2.0 and expected to create planning-related issues.
|
||||
required: true
|
||||
- type: textarea
|
||||
id: motivation
|
||||
attributes:
|
||||
label: Motivation
|
||||
description: Explain the reasoning behind this feature. What problem does it solve or what value does it add?
|
||||
placeholder: This feature will help users by...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: design
|
||||
attributes:
|
||||
label: High Level Design / Mock-ups
|
||||
description: Provide a top-level overview of the proposed solution. Include diagrams, rough mockups, or architectural notes if helpful.
|
||||
placeholder: |
|
||||
[Add your design details here]
|
||||
- Consider including diagrams
|
||||
- Add mockups if available
|
||||
- Describe the architecture
|
||||
- type: textarea
|
||||
id: acceptance-criteria
|
||||
attributes:
|
||||
label: Acceptance Criteria
|
||||
description: Define what needs to be true for this feature to be considered complete. Be as clear and measurable as possible.
|
||||
placeholder: |
|
||||
- [ ] Criterion 1
|
||||
- [ ] Criterion 2
|
||||
- [ ] Criterion 3
|
||||
validations:
|
||||
required: true
|
|
@ -0,0 +1,38 @@
|
|||
name: "[Planning] Task"
|
||||
description: "🛑 Only intended to be used by Kubeflow project managers"
|
||||
title: "[TASK] <short description>"
|
||||
labels:
|
||||
- "kind/plan-task"
|
||||
assignees: []
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to create a task!
|
||||
- type: checkboxes
|
||||
id: certification
|
||||
attributes:
|
||||
label: Certification
|
||||
description: Please confirm your role
|
||||
options:
|
||||
- label: I certify I am an Epic Owner for Kubeflow Notebooks 2.0 and expected to create planning-related issues.
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: Provide a brief explanation of what this task involves and why it's needed. Focus on the scope and intent of the work.
|
||||
placeholder: This task will...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: acceptance-criteria
|
||||
attributes:
|
||||
label: Acceptance Criteria
|
||||
description: List the specific, measurable conditions that must be met for the task to be considered complete.
|
||||
placeholder: |
|
||||
- [ ] Criterion 1
|
||||
- [ ] Criterion 2
|
||||
- [ ] Criterion 3
|
||||
validations:
|
||||
required: true
|
|
@ -0,0 +1,243 @@
|
|||
name: Slash Command Handler
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
handle-slash-command:
|
||||
if: |
|
||||
github.event.issue.pull_request == null
|
||||
&& contains('["thesuperzapper", "ederign", "andyatmiami", "paulovmr", "jenny-s51", "harshad16", "thaorell", "kimwnasptd", "liavweiss"]', github.event.comment.user.login)
|
||||
&& (
|
||||
contains(github.event.comment.body, '/add-sub-issue')
|
||||
|| contains(github.event.comment.body, '/remove-sub-issue')
|
||||
)
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Handle slash commands
|
||||
id: handle-commands
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const parseIssueNumber = (input) => {
|
||||
if (!input) return null;
|
||||
|
||||
// Handle plain number
|
||||
if (/^\d+$/.test(input)) {
|
||||
return input;
|
||||
}
|
||||
|
||||
// Handle #number format
|
||||
const hashMatch = input.match(/^#(\d+)$/);
|
||||
if (hashMatch) {
|
||||
return hashMatch[1];
|
||||
}
|
||||
|
||||
// Handle URL format
|
||||
const urlMatch = input.match(/\/issues\/(\d+)$/);
|
||||
if (urlMatch) {
|
||||
return urlMatch[1];
|
||||
}
|
||||
|
||||
throw new Error(`Could not parse issue number from input: '${input}'`);
|
||||
};
|
||||
|
||||
const getIssueNodeId = async (owner, repo, issueNumber) => {
|
||||
const response = await github.graphql(`
|
||||
query {
|
||||
repository(owner: "${owner}", name: "${repo}") {
|
||||
issue(number: ${issueNumber}) {
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
return {
|
||||
id: response.repository.issue.id,
|
||||
title: response.repository.issue.title
|
||||
};
|
||||
};
|
||||
|
||||
const performSubIssueMutation = async (action, parentIssueNodeId, childIssueNodeId) => {
|
||||
const mutationField = `${action}SubIssue`;
|
||||
|
||||
const mutation = `
|
||||
mutation {
|
||||
${mutationField}(input: {
|
||||
issueId: "${parentIssueNodeId}",
|
||||
subIssueId: "${childIssueNodeId}"
|
||||
}) {
|
||||
clientMutationId
|
||||
issue {
|
||||
id
|
||||
title
|
||||
}
|
||||
subIssue {
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
try {
|
||||
const response = await github.graphql(mutation);
|
||||
return response;
|
||||
} catch (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
const collectSubIssueOperations = async (line, action, owner, repo) => {
|
||||
const commandPrefix = `/${action}-sub-issue`;
|
||||
if (!line.startsWith(commandPrefix)) return [];
|
||||
|
||||
const args = line.replace(commandPrefix, '').trim().split(/\s+/);
|
||||
const operations = [];
|
||||
|
||||
for (const issue of args) {
|
||||
const childIssueNumber = parseIssueNumber(issue);
|
||||
const childIssue = await getIssueNodeId(owner, repo, childIssueNumber);
|
||||
operations.push({
|
||||
action,
|
||||
issueNumber: childIssueNumber,
|
||||
title: childIssue.title,
|
||||
nodeId: childIssue.id
|
||||
});
|
||||
}
|
||||
|
||||
return operations;
|
||||
};
|
||||
|
||||
const formatOperationsList = (operations, action) => {
|
||||
if (operations.length === 0) return [];
|
||||
|
||||
return [
|
||||
`### ${action} Sub-issues:`,
|
||||
...operations.map(op => `- #${op.issueNumber}`),
|
||||
''
|
||||
];
|
||||
};
|
||||
|
||||
try {
|
||||
const { owner, repo } = context.repo;
|
||||
const parentIssueNumber = context.payload.issue.number;
|
||||
const commentBody = context.payload.comment.body;
|
||||
|
||||
// Get parent issue node ID and title
|
||||
const parentIssue = await getIssueNodeId(owner, repo, parentIssueNumber);
|
||||
|
||||
// Collect all operations first
|
||||
const lines = commentBody.split('\n');
|
||||
const operations = [];
|
||||
|
||||
for (const line of lines) {
|
||||
operations.push(...await collectSubIssueOperations(line, 'add', owner, repo));
|
||||
operations.push(...await collectSubIssueOperations(line, 'remove', owner, repo));
|
||||
}
|
||||
|
||||
if (operations.length === 0) {
|
||||
return; // No valid operations found
|
||||
}
|
||||
|
||||
// Create preview comment
|
||||
const previewBodyParts = [
|
||||
':mag: **Sub-issue Operation Preview**',
|
||||
'',
|
||||
`The following operations will be performed on issue #${parentIssueNumber} (${parentIssue.title}) at the request of @${context.payload.comment.user.login}:`,
|
||||
''
|
||||
];
|
||||
|
||||
// Group operations by action for display
|
||||
const addOperations = operations.filter(op => op.action === 'add');
|
||||
const removeOperations = operations.filter(op => op.action === 'remove');
|
||||
|
||||
previewBodyParts.push(
|
||||
...formatOperationsList(addOperations, 'Adding'),
|
||||
...formatOperationsList(removeOperations, 'Removing')
|
||||
);
|
||||
|
||||
previewBodyParts.push('_This is a preview of the changes. The actual operations will be executed in the background._');
|
||||
|
||||
// Post preview comment
|
||||
const previewComment = await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: parentIssueNumber,
|
||||
body: previewBodyParts.join('\n')
|
||||
});
|
||||
|
||||
// Execute operations in original order
|
||||
for (const op of operations) {
|
||||
await performSubIssueMutation(op.action, parentIssue.id, op.nodeId);
|
||||
}
|
||||
|
||||
// Post success comment
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: parentIssueNumber,
|
||||
body: [
|
||||
':white_check_mark: **GitHub Action Succeeded**',
|
||||
'',
|
||||
`All [sub-issue operations](${previewComment.data.html_url}) requested by @${context.payload.comment.user.login} have been completed successfully.`,
|
||||
''
|
||||
].join('\n')
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
core.setOutput('error_message', error.message);
|
||||
core.setFailed(error.message);
|
||||
}
|
||||
|
||||
- name: Post error comment if failure
|
||||
if: failure()
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
try {
|
||||
const commentUrl = context.payload.comment.html_url;
|
||||
const runId = context.runId;
|
||||
const { owner, repo } = context.repo;
|
||||
const errorMessage = `${{ steps.handle-commands.outputs.error_message }}`;
|
||||
|
||||
const errorBodyParts = [
|
||||
':x: **GitHub Action Failed**',
|
||||
'',
|
||||
`The workflow encountered an error while processing [your comment](${commentUrl}) to manage sub-issues.`,
|
||||
'',
|
||||
`:point_right: [View the run](https://github.com/${owner}/${repo}/actions/runs/${runId})`,
|
||||
''
|
||||
];
|
||||
|
||||
if (errorMessage && errorMessage !== '') {
|
||||
errorBodyParts.push(
|
||||
'<details>',
|
||||
'<summary>Error details</summary>',
|
||||
'',
|
||||
'```',
|
||||
errorMessage,
|
||||
'```',
|
||||
'',
|
||||
'</details>',
|
||||
''
|
||||
);
|
||||
}
|
||||
|
||||
errorBodyParts.push('Please check the logs and try again, or open a bug report if the issue persists.');
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: context.payload.issue.number,
|
||||
body: errorBodyParts.join('\n')
|
||||
});
|
||||
} catch (error) {
|
||||
core.setFailed(`Failed to post error comment: ${error.message}`);
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
name: Validate Planning Issue
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, labeled]
|
||||
|
||||
env:
|
||||
AUTHORIZED_USERS: '["thesuperzapper", "ederign", "andyatmiami", "paulovmr", "jenny-s51", "harshad16", "thaorell", "kimwnasptd", "liavweiss"]'
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
validate-issue:
|
||||
if: |
|
||||
(github.event.action == 'labeled' && (github.event.label.name == 'kind/plan-epic' || github.event.label.name == 'kind/plan-feature' || github.event.label.name == 'kind/plan-task')) ||
|
||||
(github.event.action == 'opened' && (contains(github.event.issue.labels.*.name, 'kind/plan-epic') || contains(github.event.issue.labels.*.name, 'kind/plan-feature') || contains(github.event.issue.labels.*.name, 'kind/plan-task')))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Log trigger
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
console.log(`Action triggered by: ${context.eventName} event with action: ${context.payload.action}`);
|
||||
if (context.payload.action === 'labeled') {
|
||||
console.log(`Label added: ${context.payload.label.name}`);
|
||||
} else if (context.payload.action === 'opened') {
|
||||
console.log(`Issue opened with labels: ${context.payload.issue.labels.map(l => l.name).join(', ')}`);
|
||||
}
|
||||
|
||||
- name: Handle labeled action
|
||||
if: github.event.action == 'labeled'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const AUTHORIZED_USERS = JSON.parse(process.env.AUTHORIZED_USERS);
|
||||
const actor = context.actor;
|
||||
const issueNumber = context.issue.number;
|
||||
const addedLabel = context.payload.label.name;
|
||||
|
||||
// First check user authorization
|
||||
if (!AUTHORIZED_USERS.includes(actor)) {
|
||||
// Remove the planning label
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
name: addedLabel
|
||||
});
|
||||
|
||||
// Add a comment explaining why the label was removed
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
body: `@${actor} You are not authorized to add planning labels. Only authorized users can add planning labels to issues.`
|
||||
});
|
||||
|
||||
core.setFailed(`User ${actor} is not authorized to add planning labels`);
|
||||
}
|
||||
|
||||
console.log(`User ${actor} is authorized to add planning labels`);
|
||||
|
||||
// Then check planning label requirements
|
||||
const { data: issue } = await github.rest.issues.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber
|
||||
});
|
||||
|
||||
const currentLabels = issue.labels.map(label => label.name);
|
||||
const planningLabels = ['kind/plan-epic', 'kind/plan-feature', 'kind/plan-task'];
|
||||
const presentPlanningLabels = currentLabels.filter(label => planningLabels.includes(label));
|
||||
|
||||
if (presentPlanningLabels.length !== 1) {
|
||||
// Remove the planning label
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
name: addedLabel
|
||||
});
|
||||
|
||||
// Add a comment explaining why the label was removed
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
body: `@${actor} Planning labels require exactly one planning label to be present: ${planningLabels.join(', ')}. Please ensure only one planning label is applied.`
|
||||
});
|
||||
|
||||
core.setFailed(`Planning labels require exactly one of: ${planningLabels.join(', ')}`);
|
||||
}
|
||||
|
||||
console.log(`Issue has valid planning label: ${presentPlanningLabels[0]}`);
|
||||
|
||||
- name: Handle opened action
|
||||
if: github.event.action == 'opened'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const AUTHORIZED_USERS = JSON.parse(process.env.AUTHORIZED_USERS);
|
||||
const actor = context.actor;
|
||||
const issueNumber = context.issue.number;
|
||||
|
||||
if (!AUTHORIZED_USERS.includes(actor)) {
|
||||
// Add a comment explaining why the issue will be closed
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
body: `@${actor} You are not authorized to create planning issues. Only authorized users can create planning issues.`
|
||||
});
|
||||
|
||||
// Close the issue
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
state: 'closed'
|
||||
});
|
||||
|
||||
core.setFailed(`User ${actor} is not authorized to create planning issues`);
|
||||
}
|
||||
|
||||
console.log(`User ${actor} is authorized to create planning issues`);
|
|
@ -0,0 +1,9 @@
|
|||
# Adopters of Kubeflow Notebooks
|
||||
|
||||
Below are the adopters of the Notebooks project. If you are using Notebooks
|
||||
please add yourself into the following list by a pull request.
|
||||
Please keep the list in alphabetical order.
|
||||
|
||||
| Organization | Contact | Description of Use |
|
||||
|--------------------------------------------------|-------------------------------------------------------------------|--------------------------------------------------------|
|
||||
| [Red Hat](https://www.redhat.com/) | [@franciscojavierarceo](https://github.com/franciscojavierarceo) | Kubeflow Notebooks is part of Red Hat OpenShift AI. |
|
|
@ -1,12 +1,13 @@
|
|||
# Kubeflow Notebooks
|
||||
[](https://www.bestpractices.dev/projects/9942)
|
||||
|
||||
[Kubeflow Notebooks](https://www.kubeflow.org/docs/components/notebooks/overview/) lets you run web-based development environments on your Kubernetes cluster by running them inside Pods.
|
||||
|
||||
> ⚠️ __Note__ ⚠️
|
||||
>
|
||||
>
|
||||
> We are currently moving the _Kubeflow Notebooks 1.0_ codebase from [`kubeflow/kubeflow`](https://github.com/kubeflow/kubeflow) to this repository ([`kubeflow/notebooks`](https://github.com/kubeflow/notebooks)).
|
||||
> Please see [`kubeflow/kubeflow#7549`](https://github.com/kubeflow/kubeflow/issues/7549) for more information.
|
||||
>
|
||||
>
|
||||
> We are currently developing _Kubeflow Notebooks 2.0_ in this repository under the [`notebooks-v2`](https://github.com/kubeflow/notebooks/tree/notebooks-v2) branch.
|
||||
> Please see [`kubeflow/notebooks#85`](https://github.com/kubeflow/notebooks/issues/85) for more information.
|
||||
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Kubeflow Notebooks versions are expressed as `vX.Y.Z`, where X is the major version,
|
||||
Y is the minor version, and Z is the patch version, following the
|
||||
[Semantic Versioning](https://semver.org/) terminology.
|
||||
|
||||
The Kubeflow Notebooks project maintains release branches for the most recent two minor releases.
|
||||
Applicable fixes, including security fixes, may be backported to those two release branches,
|
||||
depending on severity and feasibility.
|
||||
|
||||
Users are encouraged to stay updated with the latest releases to benefit from security patches and
|
||||
improvements.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
We're extremely grateful for security researchers and users that report vulnerabilities to the
|
||||
Kubeflow Open Source Community. All reports are thoroughly investigated by Kubeflow projects owners.
|
||||
|
||||
You can use the following ways to report security vulnerabilities privately:
|
||||
|
||||
- Using the Kubeflow Notebooks repository [GitHub Security Advisory](https://github.com/kubeflow/notebooks/security/advisories/new).
|
||||
- Using our private Kubeflow Steering Committee mailing list: ksc@kubeflow.org.
|
||||
|
||||
Please provide detailed information to help us understand and address the issue promptly.
|
||||
|
||||
## Disclosure Process
|
||||
|
||||
**Acknowledgment**: We will acknowledge receipt of your report within 10 business days.
|
||||
|
||||
**Assessment**: The Kubeflow projects owners will investigate the reported issue to determine its
|
||||
validity and severity.
|
||||
|
||||
**Resolution**: If the issue is confirmed, we will work on a fix and prepare a release.
|
||||
|
||||
**Notification**: Once a fix is available, we will notify the reporter and coordinate a public
|
||||
disclosure.
|
||||
|
||||
**Public Disclosure**: Details of the vulnerability and the fix will be published in the project's
|
||||
release notes and communicated through appropriate channels.
|
||||
|
||||
## Prevention Mechanisms
|
||||
|
||||
Kubeflow Notebooks employs several measures to prevent security issues:
|
||||
|
||||
**Code Reviews**: All code changes are reviewed by maintainers to ensure code quality and security.
|
||||
|
||||
**Dependency Management**: Regular updates and monitoring of dependencies (e.g. Dependabot) to
|
||||
address known vulnerabilities.
|
||||
|
||||
**Continuous Integration**: Automated testing and security checks are integrated into the CI/CD pipeline.
|
||||
|
||||
**Image Scanning**: Container images are scanned for vulnerabilities.
|
||||
|
||||
## Communication Channels
|
||||
|
||||
For the general questions please join the following resources:
|
||||
|
||||
- Kubeflow [Slack channels](https://www.kubeflow.org/docs/about/community/#kubeflow-slack-channels).
|
||||
|
||||
- Kubeflow discuss [mailing list](https://www.kubeflow.org/docs/about/community/#kubeflow-mailing-list).
|
||||
|
||||
Please **do not report** security vulnerabilities through public channels.
|
Loading…
Reference in New Issue