{"id":37168710,"url":"https://github.com/digtux/laminar","last_synced_at":"2026-01-14T19:56:06.441Z","repository":{"id":48779564,"uuid":"277513040","full_name":"digtux/laminar","owner":"digtux","description":"automated (GitOps) tool for docker tag promotion in git","archived":false,"fork":false,"pushed_at":"2023-04-18T15:52:11.000Z","size":170,"stargazers_count":8,"open_issues_count":1,"forks_count":4,"subscribers_count":4,"default_branch":"main","last_synced_at":"2023-09-05T03:30:30.049Z","etag":null,"topics":["docker","ecr","fluxcd","gcr","git","github","gitops","google-artifact-registry"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/digtux.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-07-06T10:35:09.000Z","updated_at":"2023-03-16T10:04:28.000Z","dependencies_parsed_at":"2023-01-31T14:45:41.336Z","dependency_job_id":null,"html_url":"https://github.com/digtux/laminar","commit_stats":null,"previous_names":[],"tags_count":3,"template":null,"template_full_name":null,"purl":"pkg:github/digtux/laminar","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/digtux%2Flaminar","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/digtux%2Flaminar/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/digtux%2Flaminar/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/digtux%2Flaminar/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/digtux","download_url":"https://codeload.github.com/digtux/laminar/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/digtux%2Flaminar/sbom","scorecard":{"id":342823,"data":{"date":"2025-08-11","repo":{"name":"github.com/digtux/laminar","commit":"80a0973fc6d7aaf7378be066d383dbd0dc0e6651"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.9,"checks":[{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":1,"reason":"Found 3/29 approved changesets -- score normalized to 1","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Info: jobLevel 'contents' permission set to 'read': .github/workflows/docker-release.yaml:19","Warn: no topLevel permission defined: .github/workflows/docker-release.yaml:1","Warn: no topLevel permission defined: .github/workflows/go.yml:1","Info: topLevel 'contents' permission set to 'read': .github/workflows/lint.yaml:14","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: third-party GitHubAction not pinned by hash: .github/workflows/docker-release.yaml:24: update your workflow using https://app.stepsecurity.io/secureworkflow/digtux/laminar/docker-release.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/docker-release.yaml:29: update your workflow using https://app.stepsecurity.io/secureworkflow/digtux/laminar/docker-release.yaml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/docker-release.yaml:49: update your workflow using https://app.stepsecurity.io/secureworkflow/digtux/laminar/docker-release.yaml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/docker-release.yaml:55: update your workflow using https://app.stepsecurity.io/secureworkflow/digtux/laminar/docker-release.yaml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/docker-release.yaml:59: update your workflow using https://app.stepsecurity.io/secureworkflow/digtux/laminar/docker-release.yaml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/docker-release.yaml:68: update your workflow using https://app.stepsecurity.io/secureworkflow/digtux/laminar/docker-release.yaml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/docker-release.yaml:74: update your workflow using https://app.stepsecurity.io/secureworkflow/digtux/laminar/docker-release.yaml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/docker-release.yaml:85: update your workflow using https://app.stepsecurity.io/secureworkflow/digtux/laminar/docker-release.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/go.yml:21: update your workflow using https://app.stepsecurity.io/secureworkflow/digtux/laminar/go.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/go.yml:24: update your workflow using https://app.stepsecurity.io/secureworkflow/digtux/laminar/go.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/go.yml:25: update your workflow using https://app.stepsecurity.io/secureworkflow/digtux/laminar/go.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/lint.yaml:21: update your workflow using https://app.stepsecurity.io/secureworkflow/digtux/laminar/lint.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/lint.yaml:24: update your workflow using https://app.stepsecurity.io/secureworkflow/digtux/laminar/lint.yaml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/lint.yaml:26: update your workflow using https://app.stepsecurity.io/secureworkflow/digtux/laminar/lint.yaml/main?enable=pin","Warn: containerImage not pinned by hash: Dockerfile.alpine:6-7","Warn: containerImage not pinned by hash: Dockerfile.alpine:16-17: pin your Docker image by updating docker.io/alpine to docker.io/alpine@sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1","Warn: containerImage not pinned by hash: Dockerfile.kapitan:7-8","Warn: containerImage not pinned by hash: Dockerfile.kapitan:19-20","Warn: goCommand not pinned by hash: .github/workflows/go.yml:41","Info:   0 out of   6 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   8 third-party GitHubAction dependencies pinned","Info:   0 out of   4 containerImage dependencies pinned","Info:   0 out of   1 goCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 11 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"31 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GO-2022-0635","Warn: Project is vulnerable to: GO-2022-0646","Warn: Project is vulnerable to: GO-2023-1765 / GHSA-2q89-485c-9j2x","Warn: Project is vulnerable to: GO-2024-2453 / GHSA-9763-4f94-gfch","Warn: Project is vulnerable to: GO-2025-3754 / GHSA-2x5j-vhc8-9cwm","Warn: Project is vulnerable to: GHSA-hqxw-f8mx-cpmw","Warn: Project is vulnerable to: GO-2023-1699 / GHSA-232p-vwff-86mp","Warn: Project is vulnerable to: GO-2023-1700 / GHSA-33pg-m6jh-5237","Warn: Project is vulnerable to: GO-2023-1701 / GHSA-6wrf-mxfj-pf5p","Warn: Project is vulnerable to: GHSA-jq35-85cj-fj4p","Warn: Project is vulnerable to: GHSA-mq39-4gv4-mvpx","Warn: Project is vulnerable to: GO-2024-3005 / GHSA-v23v-6jw2-98fq","Warn: Project is vulnerable to: GO-2024-2512 / GHSA-xw73-rw38-6vjc","Warn: Project is vulnerable to: GO-2025-3829 / GHSA-4vq8-7jfc-9cvp","Warn: Project is vulnerable to: GO-2024-2456 / GHSA-449p-3h89-pw88","Warn: Project is vulnerable to: GO-2024-2466 / GHSA-mw99-9chc-xw7r","Warn: Project is vulnerable to: GO-2025-3367 / GHSA-r9px-m959-cxf4","Warn: Project is vulnerable to: GO-2025-3368 / GHSA-v725-9546-7q7m","Warn: Project is vulnerable to: GO-2025-3553 / GHSA-mh63-6h87-95cp","Warn: Project is vulnerable to: GO-2023-2402 / GHSA-45x7-px36-x8w8","Warn: Project is vulnerable to: GO-2024-3321 / GHSA-v778-237x-gjrc","Warn: Project is vulnerable to: GO-2025-3487 / GHSA-hcg3-q754-cr77","Warn: Project is vulnerable to: GO-2023-1988 / GHSA-2wrh-6pvc-2jm9","Warn: Project is vulnerable to: GO-2023-2102 / GHSA-4374-p667-p6c8","Warn: Project is vulnerable to: GO-2023-2153 / GHSA-m425-mq94-257g / GHSA-qppj-fm5r-hxr3","Warn: Project is vulnerable to: GO-2024-2687 / GHSA-4v7x-pqxf-cx7m","Warn: Project is vulnerable to: GO-2024-3333","Warn: Project is vulnerable to: GO-2025-3503 / GHSA-qxp5-gwg8-xv66","Warn: Project is vulnerable to: GO-2025-3595 / GHSA-vvgc-356p-c3xw","Warn: Project is vulnerable to: GO-2025-3488 / GHSA-6v2p-p543-phr9","Warn: Project is vulnerable to: GO-2024-2611 / GHSA-8r3f-844c-mc37"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-18T06:18:11.725Z","repository_id":48779564,"created_at":"2025-08-18T06:18:11.725Z","updated_at":"2025-08-18T06:18:11.725Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28433616,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T18:57:19.464Z","status":"ssl_error","status_checked_at":"2026-01-14T18:52:48.501Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["docker","ecr","fluxcd","gcr","git","github","gitops","google-artifact-registry"],"created_at":"2026-01-14T19:56:05.669Z","updated_at":"2026-01-14T19:56:06.434Z","avatar_url":"https://github.com/digtux.png","language":"Go","readme":"# Laminar\n\n## Don't let your deployments be turbulent!\n\n\n## What is this?\n\nLaminar is a reasonably simple `GitOps` tool, inspired by things like [flux](https://github.com/fluxcd/flux) but with a very different architecture.\n\nThe TL;DR of what it does is this:\n\nStep 1, you tell it about your docker registry(s):\n```yaml\ndockerRegistries:\n- reg: 1122334455.dkr.ecr.eu-west-2.amazonaws.com/acmecorp\n  name: acmecorp-aws\n```\n\nStep 2, you give it (git+ssh) access to your git repo:\n```yaml\ngit:\n- name: manifests\n  url: gitoperations@github.com:acmecorp/k8s-manifests.gitoperations\n  branch: master\n  key: ~/example_ssh_id_rsa  # path to the SSH key (needed for gitoperations)\n  pollFreq: 120              # How often to sync..\n  updates: []                # list of updates (see next step)\n```\n\nStep 3, you tell laminar which files you want it to operate on (in the same git repo)\n```yaml\ngit:\n- name: manifests\n  updates:\n\n  - pattern: \"glob:develop-*\" # will match docker tags such as \"develop-1.2\" or \"develop-short_sha\"\n    # where to look in your gitoperations repo\n    files:\n    - dev/  # laminar will search in directory \"dev\"\n```\n\nStep 4, run laminar\n```shell\nAWS_PROFILE=myprofile ./laminar\n```\n\nLaminar will then do the following:\n- clone the git repo `k8s-manifests` and checkout branch `master`\n- search for files under `dev/` inside your git repo\n- inside the files search for strings such as: `1122334455.dkr.ecr.eu-west-2.amazonaws.com/acmecorp/app-name:develop-52af76b8` (any tag that matches `develop-*`)\n- index the tags available from `1122334455.dkr.ecr.eu-west-2.amazonaws.com/acmecorp/app-name`\n- check if there are any tags (matching `develop-*`) which are more recent than `develop-52af76b8`\n- a. if there is a more recent tag in ECR (eg: `develop-b2ee56fd`) update the file from  `develop-52af76b8` -\u003e `develop-b2ee56fd`\n- b: git commit the updated file(s), then git push\n- sleep until the end of `pollFreq`\n- repeat\n\n### Main loop sequence\n```mermaid\nsequenceDiagram\nparticipant laminar as Laminar\nparticipant git as Git\nparticipant dr as Docker Registry\n\nloop every interval\n    loop for each git repo\n        laminar-\u003e\u003egit: clone \"k8s-manifest\"\n        laminar-\u003e\u003elaminar: checkout \"branch\"\n        laminar-\u003e\u003elaminar: search files for string matching \"pattern\"\n    end\n    \n    loop for each docker registry\n        laminar-\u003e\u003edr: get available images tags\n    end\n    \n    loop for each git repo\n        alt new tag found\n            laminar-\u003e\u003elaminar: replace tag\n            laminar-\u003e\u003elaminar: git commit\n            laminar-\u003e\u003egit: push\n        end\n    end\nend\n```\n\n# Reasoning\nWe love weave flux.. but it makes working with templated manifests challenging. If you're running 10x kubernetes clusters it also makes very little sense to have each one polling your docker registries.\n\n- With this pattern you only need one tool to automate your GitOps.\n- This is also compatible with docker-compose or practically any text file really.\n- easier to co-ordinate many changes at the same time.\n- Lets assume my manifests are templated in my git repo.. flux would want to patch the file that is the output of my templating. With laminar you can update both, or just one. This opens the door to using `kapitan`, `tanka`, `cuelang` or any templating really.\n\n# Features\n\n- [x] checkout your git repo(s)\n- [x] search yaml files looking for obvious docker images\n- [x] check if there are more recent docker tags are in your registry and ready to be deployed\n- [x] update your git repo with the more recent tags\n- [x] dynamically load a list of files and image:tag patterns from the remote git repos (`.laminar.yaml`)\n- [x] add `exec` action so commands can be run after modifying git (and before the `git commit`)\n- [x] docker image + deployment manifest\n- [ ] prometheus metrics\n\n\n# issues/todo\n- [ ] exclude list (users can blacklist promoting specific image+tag patterns).. feature untested\n- [ ] after initialCheckout(), if the (remote) git repo is reverted with a `--force` push we should handle that and re-clone\n- [ ] more tests, do this when refactoring the logic\n- [ ] the main loop is currently (MVP) and simply just a `time.Sleep()`. There is no concurrnecy/`time.Tick()` yet.\n- [ ] occasional errors from registry polling not surfacing correctly. probably needs some attention.\n- [ ] tidy up (specifically the business logic around change requests and add maybe add some concurrency)\n- [ ] quick start/tutorial/example docs!\n- [ ] example: PrometheusAlerts\n- [ ] example: grafana dashboard\n\n\n## optional behaviours\n- [x] glob filters on tags (eg `master-*` )\n- [x] only operate on specific files or directories in your git repo\n- [x] multiple git repos (not tested well)\n- [ ] built in \"post sync\" `actions` such as: \"slack alert\", \"github PR\"\n- [x] user configurable `actions` such as running a shell script to re-render charts\n- [ ] user adjustable file exclude list\n- [ ] an api endpoint that can trigger a sync (so your CI can hit it after pushing a new image)\n- [ ] a simple gui with some info about tags and images\n- [ ] individual auth configuration available for registries (allowing support for multiple GCR and ECR)\n- [x] other tag matching patterns, specifically: `regex`\n- [ ] other tag matching patterns, specifically: `semver`\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdigtux%2Flaminar","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdigtux%2Flaminar","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdigtux%2Flaminar/lists"}