Skip to content

Comments

feat: add git-based skill fetching with shared auth and lightweight init image#1365

Open
EItanya wants to merge 6 commits intomainfrom
feat/git-skills-shared-auth
Open

feat: add git-based skill fetching with shared auth and lightweight init image#1365
EItanya wants to merge 6 commits intomainfrom
feat/git-skills-shared-auth

Conversation

@EItanya
Copy link
Contributor

@EItanya EItanya commented Feb 23, 2026

Summary

  • Add support for fetching agent skills from Git repositories alongside existing OCI image refs
  • Introduce a single shared gitAuthSecretRef on SkillForAgent that applies credentials to all gitRefs entries (supports both HTTPS token and SSH key auth)
  • Replace two separate init containers (alpine/git for git + the full ~1GB kagent runtime for OCI) with a single lightweight skills-init image (~30MB) containing only git and krane
  • Harden the init script against shell injection using heredoc variable assignments, and add server-side validation for subPath traversal and duplicate skill names

New CRD Fields

spec:
  skills:
    # Existing: OCI skill image refs
    refs:
      - ghcr.io/org/my-skill:v1

    # NEW: shared git auth secret (HTTPS token or SSH key)
    gitAuthSecretRef:
      name: my-git-creds

    # NEW: git repositories to clone as skills
    gitRefs:
      - url: https://github.com/org/my-skills.git
        ref: v2.0.0           # branch, tag, or commit SHA (default: main)
        path: skills/k8s      # optional subdirectory
        name: k8s-skill       # optional custom directory name

Auth Secret Format

The secret referenced by gitAuthSecretRef should contain either:

  • token key — for HTTPS credential helper (x-access-token)
  • ssh-privatekey key — for SSH-based cloning

Changes by Area

CRD (go/api/v1alpha2/agent_types.go)

  • Add GitRepo type with url, ref, path, and name fields
  • Add gitAuthSecretRef and gitRefs fields to SkillForAgent
  • Regenerate CRDs and Helm chart CRD templates

Controller / Translator (go/internal/controller/translator/agent/)

  • Add buildSkillsInitContainer — unified init container for both git and OCI skills
  • Add skills-init.sh.tmpl — shell script template using heredoc variables to prevent injection
  • Add validateSubPath() — rejects absolute paths and .. traversal
  • Add duplicate skill directory name detection across git and OCI refs
  • Add gitSkillName() with url.Parse() to strip query params/fragments
  • Add ociSkillName() for extracting names from OCI refs
  • Add DefaultSkillsInitImageConfig and ImageConfig.Image() method

Lightweight Init Image (docker/skills-init/)

  • New multi-stage Dockerfile: builds krane from source, produces a minimal Alpine image with just git + krane
  • Replaces use of the full kagent runtime image for OCI skill pulling

Helm

  • Add skillsInitImage config (registry, repository, tag, pullPolicy) to values.yaml
  • Wire through controller ConfigMap

Tests

  • Golden tests for agent_with_git_skills (git + OCI + auth + subpath + commit SHA)
  • Integration tests for all skill init container scenarios
  • Unit tests for ociSkillName, gitSkillName, validateSubPath, and duplicate name detection

Test plan

  • cd go && go build ./... compiles
  • cd go && go test ./internal/controller/translator/agent/... -count=1 — all tests pass
  • Golden files match expected output (UPDATE_GOLDEN=true to regenerate)
  • Deploy to Kind cluster with an agent using gitRefs and verify skills are cloned
  • Verify OCI skills still work with the new unified init container
  • Verify auth secret (HTTPS token) works for private repos

🤖 Generated with Claude Code

Add support for cloning skills from Git repositories in agent init
containers. A single gitAuthSecretRef on SkillForAgent applies to all
gitRefs entries, matching the pattern used by InsecureSkipVerify for
OCI skill refs.

- Add GitRepo type and GitAuthSecretRef field to SkillForAgent CRD
- Add git-init.sh.tmpl script template for cloning repos
- Add buildGitSkillsInitContainer to create the init container
- Support branch, tag, commit SHA refs, subdirectory paths, and
  custom skill names
- Add configurable git init image (--git-init-image flag)
- Add unit and golden tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Eitan Yarmush <eitan.yarmush@solo.io>
Copilot AI review requested due to automatic review settings February 23, 2026 18:11
@EItanya EItanya force-pushed the feat/git-skills-shared-auth branch from f5a19ac to 19173b7 Compare February 23, 2026 18:18
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds support for fetching agent skills from Git repositories with optional shared authentication, in addition to the existing OCI registry support. The implementation introduces a new unified skills-init container that handles both Git and OCI skill sources, replacing the previous approach that used the agent image itself for skill pulling.

Changes:

  • Adds gitRefs and gitAuthSecretRef fields to the SkillForAgent CRD to support Git-based skills with optional authentication
  • Creates a new skills-init Docker image with Git and krane (OCI tool) for fetching skills
  • Implements a unified init container that handles both Git clones and OCI pulls via a shell script template
  • Removes the minItems: 1 constraint from the refs field to allow Git-only skill configurations

Reviewed changes

Copilot reviewed 40 out of 41 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
go/api/v1alpha2/agent_types.go Adds GitRepo type and new fields (gitRefs, gitAuthSecretRef) to SkillForAgent
go/config/crd/bases/kagent.dev_agents.yaml Updates CRD schema with new git skill fields and removes minItems constraint
helm/kagent-crds/templates/kagent.dev_agents.yaml Mirrors CRD schema changes
go/internal/controller/translator/agent/adk_api_translator.go Implements unified skills init container with git and OCI support
go/internal/controller/translator/agent/skills-init.sh.tmpl Shell script template for fetching skills from Git and OCI
docker/skills-init/Dockerfile New Docker image with git and krane tools
go/pkg/app/app.go Adds command-line flags for skills-init image configuration
helm/kagent/values.yaml Adds skillsInitImage configuration section
helm/kagent/templates/controller-configmap.yaml Adds environment variables for skills-init image configuration
Makefile Adds build target for skills-init image and updates push target
go/internal/controller/translator/agent/git_skills_test.go Comprehensive test coverage for git skills functionality
go/internal/controller/translator/agent/testdata/* Test fixtures and golden files for git skills
.gitignore Changes env/ pattern to .env/

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Replace two separate init containers (git-skills-init using alpine/git
and skills-init using the full ~1GB kagent runtime image) with a single
skills-init container using a new lightweight image (~30MB) containing
only git and krane. OCI skill pulling now uses krane directly in a shell
script instead of shelling out through Python, eliminating the need to
pull the full runtime image just to run a Go binary.

- Add docker/skills-init/Dockerfile (alpine + git + krane)
- Add unified skills-init.sh.tmpl handling both git and OCI skills
- Merge buildGitSkillsInitContainer and OCI init into buildSkillsInitContainer
- Add ImageConfig.Image() method and DefaultSkillsInitImageConfig
- Add ociSkillName() helper for extracting skill names from OCI refs
- Rename git-init-image flag/config to structured skills-init-image-*
- Add build-skills-init target to Makefile
- Regenerate CRDs and golden test files

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Eitan Yarmush <eitan.yarmush@solo.io>
@EItanya EItanya force-pushed the feat/git-skills-shared-auth branch from 19173b7 to 1403147 Compare February 23, 2026 18:23
EItanya and others added 2 commits February 23, 2026 18:24
Signed-off-by: Eitan Yarmush <eitan.yarmush@solo.io>
- Use heredoc variable assignments in shell template instead of inline
  single-quoted values to prevent shell injection via values containing
  single quotes
- Add echo logging before git clone and krane export operations
- Stop suppressing ssh-keyscan stderr (remove 2>/dev/null)
- Validate SubPath: reject absolute paths and ".." traversal segments
- Detect duplicate skill directory names across git and OCI refs
- Use url.Parse in gitSkillName to strip query params and fragments
- Add unit tests for ociSkillName, gitSkillName, validateSubPath, and
  duplicate name detection
- Remove unused Makefile push target

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Eitan Yarmush <eitan.yarmush@solo.io>
@EItanya EItanya changed the title Feat/git skills shared auth feat: add git-based skill fetching with shared auth and lightweight init image Feb 23, 2026
Without this, users could create a Skills object with neither OCI refs
nor git refs, resulting in unnecessary init containers and volumes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Eitan Yarmush <eitan.yarmush@solo.io>
AllowedNamespaces *AllowedNamespaces `json:"allowedNamespaces,omitempty"`
}

// +kubebuilder:validation:XValidation:rule="size(self.refs) > 0 || size(self.gitRefs) > 0",message="at least one of refs or gitRefs must be specified"
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: You can use AtLeastOneOf here.


RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /build/krane .

### Stage 1: runtime
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: No need for comments here. They're implicit based on FROM structuring.

InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty"`

// The list of skill images to fetch.
// +kubebuilder:validation:MinItems=1
Copy link
Contributor

Choose a reason for hiding this comment

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

Why remove this? If it's +optional, you can still enforce "at least one item should be present".

Comment on lines +103 to +114
// Git reference: branch name, tag, or commit SHA.
// +optional
// +kubebuilder:default="main"
Ref string `json:"ref,omitempty"`

// Subdirectory within the repo to use as the skill root.
// +optional
Path string `json:"path,omitempty"`

// Name for the skill directory under /skills. Defaults to the repo name.
// +optional
Name string `json:"name,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

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

Take a peek at https://github.com/kubernetes-sigs/kube-api-linter/. All +optional fields should be a pointer to a string. We can also take this opportunity to define simple validation checks too, e.g. minLength, patterns, etc.

GitAuthSecretRef *corev1.LocalObjectReference `json:"gitAuthSecretRef,omitempty"`

// Git repositories to fetch skills from.
// +kubebuilder:validation:MaxItems=20
Copy link
Contributor

Choose a reason for hiding this comment

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

MinItems?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, because it can be either gitRefs or refs

Comment on lines +84 to +89
// Reference to a Secret containing git credentials.
// Applied to all gitRefs entries.
// The secret should contain a `token` key for HTTPS auth,
// or `ssh-privatekey` for SSH auth.
// +optional
GitAuthSecretRef *corev1.LocalObjectReference `json:"gitAuthSecretRef,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

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

This is slightly awkward to me. The intention is that this field decorates the gitRefs field? Couldn't we achieve the same thing with a top-level field where we decorate references with auth instead?

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 agree that this API is awkward. I think unfortunately this will be a v1alpha3 type of thing. Ideally we'd have a list of items which have a oneof. Either OCI refs or GitHub refs. The issue is that the auth mechanisms are different so I don't think we can realistically use the same secret for both which is very annoying

Comment on lines 431 to 439
if hasSkills {
insecure := agent.Spec.Skills != nil && agent.Spec.Skills.InsecureSkipVerify
container, skillsVolumes, err := buildSkillsInitContainer(gitRefs, gitAuthSecretRef, skills, insecure, dep.SecurityContext)
if err != nil {
return nil, fmt.Errorf("failed to build skills init container: %w", err)
}
initContainers = append(initContainers, container)
volumes = append(volumes, skillsVolumes...)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: can't we combine these two if hasSkills conditionals?

Comment on lines +1393 to +1404
func validateSubPath(p string) error {
if p == "" {
return nil
}
if path.IsAbs(p) {
return fmt.Errorf("skill subPath must be relative, got %q", p)
}
if slices.Contains(strings.Split(p, "/"), "..") {
return fmt.Errorf("skill subPath must not contain '..', got %q", p)
}
return nil
}
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 like this is the sort of thing that if we use kube validation for on the API would be much more explicit and reduce the need to maintain and test such code.

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 agree in theory, but the logic here is a bit too involved for cel I think. Too much string manipulation

Comment on lines +1491 to +1494
name := gitSkillName(ref)
if seen[name] {
return skillsInitData{}, fmt.Errorf("duplicate skill directory name %q", name)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be possible to use kube validation here to ensure that the individual gitRef items are unique? This should allow us to never have to worry about duplicate skill directory names and simplify this code.

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 think combining data from 2 lists and then checking uniqueness is a bit too complex for CEL. Most likely we should do best effort here, but I think we can start with this. What do you think?

Copy link
Contributor

@iplay88keys iplay88keys Feb 24, 2026

Choose a reason for hiding this comment

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

Do the directory names need to be defined by the user? What if we just generated them for the user in a similar way to how go mod downloaded libraries work. Splitting the ref into a folder structure like: /skills/<org>/<repo>@<tag>?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It needs to be skills/<skill-name> because of how skills are organized on the filesystem

Copy link
Contributor

Choose a reason for hiding this comment

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

makes sense to me

Signed-off-by: Eitan Yarmush <eitan.yarmush@solo.io>
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.

4 participants