Skip to content

Conversation

@alexellis
Copy link
Member

@alexellis alexellis commented Nov 13, 2025

Description

Version pinning for templates

Motivation and Context

It's risky for us to make breaking changes are made to templates.

Versioning is available for all the functions within a folder by adding a templates.configuration section along with a custom source and a #v0.1.0 fragment to the git URL.

But it isn't possible to have different functions pinned to different versions of templates.

For instance:

1 - Use "head"/latest (as now)
2 - Use a specific branch or git tag aka "release"
3 - Use a specific SHA hash from a commit in HEAD/master

Testing with a templates source given

version: 1.0
provider:
  name: openfaas
  gateway: http://127.0.0.1:8080

functions:
  go-latest:
    lang: golang-middleware
    handler: ./go-latest
    image: ttl.sh/alexellis/go-latest:latest

  go-090:
    lang: [email protected]
    handler: ./go-090
    image: ttl.sh/alexellis/go-090:latest

  go-sha-2e6e262:
    lang: golang-middleware@sha-2e6e262
    handler: ./go-sha-2e6e262
    image: ttl.sh/alexellis/go-sha-2e6e262:latest


configuration:
  templates:
   - name: golang-middleware
     source: https://github.com/openfaas/golang-http-template

Testing without a templates source given

version: 1.0
provider:
  name: openfaas
  gateway: http://127.0.0.1:8080

functions:
  go-latest:
    lang: golang-middleware
    handler: ./go-latest
    image: ttl.sh/alexellis/go-latest:latest

  go-090:
    lang: [email protected]
    handler: ./go-090
    image: ttl.sh/alexellis/go-090:latest

  go-sha-2e6e262:
    lang: golang-middleware@sha-2e6e262
    handler: ./go-sha-2e6e262
    image: ttl.sh/alexellis/go-sha-2e6e262:latest

Testing via new with a store template

rm -rf template ; ./faas-cli new --lang python3-http --prefix ttl.sh/alexellis pyfn1

How Has This Been Tested?

The above YAML

Types of changes

This affects: new, build, publish, template pull

Checklist:

  • My change requires a change to the documentation.

Documentation will need an update, but for most users there will be no change unless they opt into versioning.

@alexellis alexellis force-pushed the pin-template branch 2 times, most recently from 4e0cc29 to e88116b Compare November 14, 2025 10:22
@alexellis alexellis requested a review from Copilot November 14, 2025 17:12
Copilot finished reviewing on behalf of alexellis November 14, 2025 17:15
Copy link

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 implements version pinning for templates, allowing functions to specify different versions of templates using three methods: using latest/head, a specific branch or git tag, or a specific SHA hash. The syntax uses the @ symbol (e.g., [email protected] or golang-middleware@sha-2e6e262).

Key Changes:

  • Added support for version-pinned template references using @ syntax in function language specifications
  • Implemented SHA-based checkout for templates with sha- prefix
  • Changed default --overwrite flag behavior from false to true for template pull commands
  • Refactored template pulling logic to handle missing templates and version references

Reviewed Changes

Copilot reviewed 37 out of 39 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
versioncontrol/parse.go Fixed typo: pinCharaterpinCharacter; updated parsing logic
versioncontrol/parse_test.go Updated tests to use corrected pinCharacter constant
versioncontrol/git.go Added GitCloneBranch, GitCloneFullDepth, and GetGitSHAFor functions
versioncontrol/core.go Added debug logging for git commands
commands/fetch_templates.go Implemented SHA checkout logic, template metadata writing, and version suffix handling
commands/template_pull.go Changed default overwrite to true; added silence flags
commands/template_pull_stack.go Refactored to use getMissingTemplates and handle versioned templates
commands/template_store_pull.go Added version reference extraction and handling
commands/new_function.go Added support for versioned template references in new function creation
commands/build.go Integrated missing template detection and pulling with version support
commands/publish.go Added template pulling logic with version support
builder/build.go Extracted template lookup into getTemplate function
builder/copy.go Improved error handling for directory creation
testing/* Added test configuration files and function handlers
config/config_file.go Improved error handling for directory creation
commands/local_run.go Improved error handling for secrets directory creation
commands/plugin_get.go Fixed error check: os.ErrExist != err!os.IsExist(err)
commands/watch.go Removed unused blank identifier in range loop
commands/secret_apply.go New command for applying secrets from .secrets folder

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

Comment on lines 200 to 208
if canWriteLanguage(existingLanguages, language, overwriteTemplate) {
// Do cp here
languageSrc := filepath.Join(extractedPath, TemplateDirectory, language)
languageDest := filepath.Join(localTemplatesDir, language)
langName := language
if refName != "" {
languageDest += "@" + refName
langName = language + "@" + refName
}
Copy link

Copilot AI Nov 14, 2025

Choose a reason for hiding this comment

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

The logic for handling versioned templates appears incomplete. When a template name includes a version suffix (e.g., [email protected]), the destination path is set to language+"@"+refName (line 206), but getMissingTemplates in template_pull_stack.go checks for function.Language which would be the full versioned name. This means the template directory structure uses @ in directory names, but there's no clear handling of how to look up these versioned templates when building. The getTemplate function in builder/build.go looks for exact directory matches by lang name, which may not work correctly with version suffixes.

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

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

getTemplate uses the expanded name so matches the directory exactly.

}
return pullStackTemplates(templatesConfig, cmd)

return pullStackTemplates([]string{}, templatesConfig, cmd)
Copy link

Copilot AI Nov 14, 2025

Choose a reason for hiding this comment

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

The function signature has changed from pullStackTemplates(templateInfo []stack.TemplateSource, cmd *cobra.Command) to pullStackTemplates(missingTemplates []string, templateSources []stack.TemplateSource, cmd *cobra.Command). However, the first parameter name missingTemplates suggests it should be a filtered list of missing templates, but at line 44 an empty slice is passed. This inconsistency could indicate a logic error or misunderstanding of the function's purpose.

Suggested change
return pullStackTemplates([]string{}, templatesConfig, cmd)
// Read stack config to get functions map
stackConfig, err := readStackConfig()
if err != nil {
return err
}
// Determine missing templates
missingTemplates, err := getMissingTemplates(stackConfig.StackConfig.Functions, "./template")
if err != nil {
return err
}
return pullStackTemplates(missingTemplates, templatesConfig, cmd)

Copilot uses AI. Check for mistakes.
Comment on lines +104 to +114
func getMissingTemplates(functions map[string]stack.Function, templatesDir string) ([]string, error) {
var missing []string

for _, function := range functions {
templatePath := fmt.Sprintf("%s/%s", templatesDir, function.Language)
if _, err := os.Stat(templatePath); err != nil && os.IsNotExist(err) {
newTemplates = append(newTemplates, info)
missing = append(missing, function.Language)
}
}

return newTemplates, nil
return missing, nil
Copy link

Copilot AI Nov 14, 2025

Choose a reason for hiding this comment

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

The function name getMissingTemplates returns a list of strings representing missing template names, but the function iterates over functions and checks their Language property. This means the function assumes that each function's language directly maps to a template name. However, with the new version pinning feature (e.g., [email protected]), the language field may include version suffixes. The function doesn't handle extracting the base template name from versioned language specifications, which could cause template lookups to fail.

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

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

template.yml cannot contain an @ suffix.

Templates can now be pinned in three ways:
- no pinning - get HEAD from default branch
- Git tag / branch name - pass @ then the release i.e. 0.9.0
- Git SHA - requires full depth clone of whole respository
followed by a git checkout

Tested using the following:

* Via build and publish with a templates configuration in
stack.yaml
* Via build and publish with no templates configuration in
stack.yaml - the templates were resolved via the store
* Via new - with and without a template being present -
the template is pulled from the store if not found locally

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <[email protected]>
@openfaas openfaas deleted a comment from reviewfn bot Nov 14, 2025
@alexellis
Copy link
Member Author

Need to document:

@openfaas openfaas deleted a comment from reviewfn bot Nov 14, 2025
@openfaas openfaas deleted a comment from reviewfn bot Nov 14, 2025
@reviewfn
Copy link

reviewfn bot commented Nov 14, 2025

AI Pull Request Overview

Summary

  • Implements version pinning for OpenFaaS templates using @ syntax (e.g., [email protected] or golang-middleware@sha-abcdef)
  • Supports tag-based versioning (@tag) and commit SHA pinning (@sha-xxxxxx)
  • Updates template fetching to handle SHA checkouts with full git history cloning
  • Modifies build process to locate versioned template directories
  • Adds metadata tracking for pulled templates with repository, ref, and SHA information
  • Enhances error handling and logging throughout the template management system
  • Includes comprehensive test cases for versioned template scenarios

Summary per file

Summary per file
File path Summary
builder/build.go Refactored BuildImage to use filesystem-based template validation instead of SDK method
builder/copy.go Minor updates to error handling in file copying operations
commands/build.go Added --disable-stack-pull flag for build command
commands/fetch_templates.go Major overhaul: added SHA support, metadata writing, improved error handling
commands/fetch_templates_test.go Updated tests for new fetch_templates functionality
commands/local_run.go Minor flag addition for stack pull disable
commands/new_function.go Added parsing for @Version syntax in language field
commands/plugin_get.go Minor updates
commands/publish.go Added --disable-stack-pull flag
commands/secret_apply.go Minor updates
commands/template_pull.go Minor updates
commands/template_pull_stack.go Minor updates
commands/template_pull_stack_test.go Updated tests for stack template pulling
commands/template_pull_test.go Minor test updates
commands/template_store_pull.go Added @ parsing for store template names
commands/watch.go Minor updates
config/config_file.go Improved error handling in directory creation
testing/.gitignore Updated to ignore versioned test directories
testing/go-090/go.mod Test template for version 0.9.0
testing/go-090/handler.go Test handler
testing/go-latest/go.mod Test template for latest version
testing/go-latest/handler.go Test handler
testing/go-sha-2e6e262/go.mod Test template for SHA version
testing/go-sha-2e6e262/handler.go Test handler
testing/inproc-fn/go.mod Test template
testing/inproc-fn/handler.go Test handler
testing/no-template-config.yaml Test config without templates
testing/pyfn/handler.py Test handler
testing/pyfn/handler_test.py Test file
testing/pyfn/requirements.txt Test requirements
testing/pyfn/tox.ini Test config
testing/stack.yaml Test stack with versioned languages
testing/template-config.yaml Test template config
vendor/modules.txt Updated Go module dependencies
versioncontrol/core.go Minor updates
versioncontrol/git.go Minor updates
versioncontrol/parse.go Fixed typo in constant name
versioncontrol/parse_test.go Minor updates

Overall Assessment

Overall Assessment

This PR successfully implements template version pinning, a valuable feature for ensuring reproducible builds and avoiding breaking changes from template updates. The implementation follows Go and CLI best practices, with good error handling and testing. However, there are some gaps in the stack-based template pulling for versioned templates, and the feature's impact on existing workflows needs careful consideration.

Strengths:

  • Clean @ syntax consistent with package managers
  • Comprehensive SHA and tag support
  • Good test coverage with realistic scenarios
  • Improved error messages and logging
  • Metadata tracking for auditability

Concerns:

  • Incomplete support for versioned templates in stack pull operations
  • Potential performance impact from full-depth cloning for SHA references
  • Migration path not clearly documented for existing users
  • Security implications of fetching arbitrary Git refs

Recommendations:

  • Complete the stack pull implementation for versioned templates
  • Add documentation for migration and best practices
  • Consider caching mechanisms to mitigate performance concerns
  • Implement validation for trusted template sources

Detailed Review

Detailed Review

Architecture and Design

Positive Aspects:

  • The @ syntax is intuitive and consistent with established package management conventions (npm, Go modules)
  • Separation of concerns: version parsing in versioncontrol, fetching in commands/fetch_templates.go, usage in builder
  • Metadata tracking provides good auditability for deployed templates

Issues:

  • The stack.yaml configuration example in the PR description shows name: golang-middleware but functions using lang: [email protected]. This creates a mismatch where faas-cli template pull stack won't pull versioned templates because getMissingTemplates returns "[email protected]" but the config lookup expects exact name matches.

Recommendation: Either:

  1. Allow config names to include @ syntax: name: [email protected]
  2. Or modify pullStackTemplates to parse the base name from versioned languages and pull the base template

Code Quality

fetch_templates.go Changes:

  • Good: Added proper error wrapping with fmt.Errorf and %w verb
  • Good: SHA validation with regex ensures only valid hex strings
  • Good: Debug logging for git operations
  • Concern: The TemplateDirectory constant change from "./template/" to "./template/" (just removed trailing slash) - inconsistent with usage elsewhere

build.go Refactoring:

  • The getTemplate function duplicates logic from the SDK's IsValidTemplate but adds filesystem checking
  • This change makes the build process more resilient to SDK changes but increases coupling to filesystem structure

Error Handling:

  • Improved throughout with better error messages
  • Consistent use of fmt.Errorf with error wrapping

Testing

Coverage:

  • Good test scenarios for SHA, tag, and latest versions
  • Realistic test data with actual template structures

Gaps:

  • No tests for the stack pull functionality with versioned templates
  • Missing integration tests for the full workflow (new -> build -> deploy)

Performance Considerations

  • SHA-based fetching requires --depth=full cloning, which is slower and uses more bandwidth
  • For large template repositories, this could be a significant performance regression
  • Consider implementing a caching layer or shallow clone with deepen as needed

Security

  • Fetching arbitrary Git refs (including SHAs) could potentially pull malicious code
  • No validation of template source trustworthiness
  • Recommend adding allowlists or signature verification for production use

Backward Compatibility

  • Existing stack files without @ syntax continue to work
  • Default behavior pulls "latest" when no version specified
  • However, users relying on always getting the latest templates may be surprised when templates are pinned

Documentation

  • PR description provides good examples
  • Testing YAML files serve as documentation
  • Missing: Migration guide for existing users
  • Missing: Best practices for when to pin vs use latest

Specific Code Comments

commands/fetch_templates.go:52-67

if len(refName) > 0 && strings.HasPrefix(refName, ShaPrefix) {
    targetCommit := strings.TrimPrefix(refName, ShaPrefix)
    // ... validation and checkout
}
  • Good validation of SHA format
  • Consider extracting SHA validation to a utility function for reuse

commands/new_function.go:143-147

_, ref, _ := strings.Cut(language, "@")
if info.TemplateName == language || (info.TemplateName+"@"+ref == language) {
  • This logic allows matching base templates when versioned name is used
  • Good fallback behavior

versioncontrol/parse.go:56

remoteURL, refName, _ = strings.Cut(repoURL, pinCharacter)
  • Modern Go idiom using strings.Cut instead of index manipulation
  • Good improvement over previous manual parsing

Recommendations for Future Work

  1. Complete Stack Integration: Update getMissingTemplates and pullStackTemplates to properly handle versioned template names in stack configurations

  2. Performance Optimization: Implement smart cloning strategies:

    • Use shallow clones for tags
    • Cache frequently used SHAs
    • Add --depth option for user control
  3. Security Hardening:

    • Add template source validation
    • Consider signed templates or allowlists
    • Document security implications
  4. User Experience:

    • Add faas-cli template list to show available versions
    • Improve error messages when versioned templates are missing
    • Add validation in faas-cli validate for template availability
  5. Monitoring: Consider adding metrics for template fetch success/failure rates and performance

Overall, this is a solid implementation of an important feature. The main blocker is the incomplete stack pull support for versioned templates, which should be addressed before merging.

/home/ubuntu/workdir/REVIEW.md

AI agent details.

Agent processing time: 1m24.273s
Environment preparation time: 6.872s
Total time from webhook: 1m35.379s

@openfaas openfaas deleted a comment from reviewfn bot Nov 19, 2025
Signed-off-by: Han Verstraete (OpenFaaS Ltd) <[email protected]>
@reviewfn
Copy link

reviewfn bot commented Nov 19, 2025

AI Pull Request Overview

Summary

  • Introduces template version pinning via @tag, @Branch, or @sha-xxx in function lang field
  • Supports pinning for both store and custom template sources
  • Adds versioncontrol package for git reference parsing
  • Implements SHA pinning with full repository cloning
  • Includes test configurations demonstrating pinned templates

Summary per file

Summary per file
File path Summary
commands/new_function.go Parses @ref from language and converts to # for template pulls
commands/fetch_templates.go Adds SHA ref handling with full clone and git checkout
commands/template_pull_stack.go Updates to pull templates from stack configuration
versioncontrol/core.go Defines git clone commands for different ref types
versioncontrol/git.go Implements git operations for cloning and SHA retrieval
versioncontrol/parse.go Parses pinned git URLs with # fragments
versioncontrol/parse_test.go Tests for ref parsing functionality
testing/template-config.yaml Test stack config with @ pinned languages
testing/go-090/, testing/go-latest/, testing/go-sha-2e6e262/ Test function directories for different pin types
Other modified files Minor updates to template fetching and validation

Overall Assessment

Overall Assessment

The PR successfully adds template versioning functionality, addressing the risk of unexpected breaking changes in templates. The implementation provides three pinning methods (tag/branch, SHA) and integrates well with existing store and custom source workflows. The versioncontrol package adds robust git ref handling. However, the implementation has critical gaps in custom source pinning support, leading to inconsistent behavior and potential runtime failures when @ refs are used with configured template sources.

Detailed Review

Detailed Review

Custom Source Pinning Logic Flaws

In commands/template_pull_stack.go, the pullStackTemplates function fails to properly handle @ refs when custom template sources are configured. The matching logic if config.Name == val cannot succeed because val (from function.Language) includes the @ref suffix while config.Name does not. This causes the code to fall back to store pulls even when a custom source is specified, defeating the purpose of custom configurations.

For example, with:

configuration:
  templates:
   - name: golang-middleware
     source: https://github.com/openfaas/golang-http-template
functions:
  go-090:
    lang: [email protected]

The function will attempt to pull from the store instead of applying the @0.9.0 ref to the custom source.

Directory Path Resolution Issues

commands/fetch_templates.go moveTemplates function does not consistently append ref suffixes to destination directories. When templateName is provided (specific template pull), languageDest is set to templateName without @ref, but functions with pinned langs expect directories like [email protected]. This mismatch will cause template resolution failures during build/publish operations.

Incomplete @ to # Conversion

commands/fetch_templates.go pullTemplate function lacks logic to parse @ refs from templateName and convert them to # fragments in repository URLs. This prevents correct ref application for custom sources, as the parsing only handles # in URLs, not @ in template names.

Test Coverage Deficiencies

commands/template_pull_stack_test.go contains no test cases for @ ref handling in template names. The new testing/template-config.yaml demonstrates @ usage but relies on store fallback behavior rather than testing custom source pinning. This leaves critical code paths untested.

SHA Pinning Performance Impact

SHA pinning requires full repository cloning (GitCloneFullDepth), which is significantly slower and more resource-intensive than shallow clones used for tags/branches. While necessary for SHA access, this could impact CI/CD performance for large repositories. Consider documenting this trade-off.

Recommendations

  1. Fix Custom Source Matching: Update pullStackTemplates to parse base names from val and match against config.Name, then construct repository URLs with #ref for custom sources.

  2. Standardize Directory Naming: Modify moveTemplates to always append @ref to languageDest when refName is present, ensuring consistent directory structures.

  3. Add @ Parsing: Implement @ to # conversion in pullTemplate for templateName refs.

  4. Expand Test Coverage: Add test cases in template_pull_stack_test.go for @ ref scenarios with both store and custom sources.

  5. Documentation: Update CLI help and documentation to clarify the difference between @ (function-level) and # (source-level) pinning.

  6. Error Handling: Add validation for invalid ref formats (e.g., non-hex SHA values) with clear error messages.

The core functionality is sound and the feature addresses a real user need. With the identified fixes, this will provide robust template versioning across all supported scenarios.

AI agent details.

Agent processing time: 1m27.809s
Environment preparation time: 6.284s
Total time from webhook: 1m38.521s

@alexellis alexellis merged commit 6e28e11 into master Nov 19, 2025
3 checks passed
@alexellis alexellis deleted the pin-template branch November 19, 2025 16:41
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.

3 participants