Skip to content
Commits on Source (49)
pkgbase = flux-bin
pkgname = flux-bin
pkgdesc = Open and extensible continuous delivery solution for Kubernetes
pkgver = ${PKGVER}
pkgrel = ${PKGREL}
......@@ -9,7 +8,15 @@ pkgname = flux-bin
arch = armv7h
arch = aarch64
license = APACHE
source_x86_64 = ${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${pkgver}/flux_${pkgver}_linux_amd64.tar.gz
source_armv6h = ${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${pkgver}/flux_${pkgver}_linux_arm.tar.gz
source_armv7h = ${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${pkgver}/flux_${pkgver}_linux_arm.tar.gz
source_aarch64 = ${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${pkgver}/flux_${pkgver}_linux_arm64.tar.gz
optdepends = bash-completion: auto-completion for flux in Bash
optdepends = zsh-completions: auto-completion for flux in ZSH
source_x86_64 = ${PKGNAME}-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${PKGVER}/flux_${PKGVER}_linux_amd64.tar.gz
sha256sums_x86_64 = ${SHA256SUM_AMD64}
source_armv6h = ${PKGNAME}-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${PKGVER}/flux_${PKGVER}_linux_arm.tar.gz
sha256sums_armv6h = ${SHA256SUM_ARM}
source_armv7h = ${PKGNAME}-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${PKGVER}/flux_${PKGVER}_linux_arm.tar.gz
sha256sums_armv7h = ${SHA256SUM_ARM}
source_aarch64 = ${PKGNAME}-${PKGVER}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${PKGVER}/flux_${PKGVER}_linux_arm64.tar.gz
sha256sums_aarch64 = ${SHA256SUM_ARM64}
pkgname = flux-bin
......@@ -8,8 +8,8 @@ pkgdesc="Open and extensible continuous delivery solution for Kubernetes"
url="https://fluxcd.io/"
arch=("x86_64" "armv6h" "armv7h" "aarch64")
license=("APACHE")
optdepends=('bash-completion: auto-completion for flux in Bash',
'zsh-completions: auto-completion for flux in ZSH')
optdepends=('bash-completion: auto-completion for flux in Bash'
'zsh-completions: auto-completion for flux in ZSH')
source_x86_64=(
"${pkgname}-${pkgver}.tar.gz::https://github.com/fluxcd/flux2/releases/download/v${pkgver}/flux_${pkgver}_linux_amd64.tar.gz"
)
......
......@@ -4,11 +4,16 @@ on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
branches: [ main, oci ]
jobs:
kind:
runs-on: ubuntu-latest
services:
registry:
image: registry:2
ports:
- 5000:5000
steps:
- name: Checkout
uses: actions/checkout@v3
......@@ -168,6 +173,36 @@ jobs:
- name: flux delete source git
run: |
/tmp/flux delete source git podinfo --silent
- name: flux oci artifacts
run: |
/tmp/flux push artifact oci://localhost:5000/fluxcd/flux:${{ github.sha }} \
--path="./manifests" \
--source="${{ github.repositoryUrl }}" \
--revision="${{ github.ref }}/${{ github.sha }}"
/tmp/flux tag artifact oci://localhost:5000/fluxcd/flux:${{ github.sha }} \
--tag latest
/tmp/flux list artifacts oci://localhost:5000/fluxcd/flux
- name: flux oci repositories
run: |
/tmp/flux create source oci podinfo-oci \
--url oci://ghcr.io/stefanprodan/manifests/podinfo \
--tag-semver 6.1.x \
--interval 10m
/tmp/flux create kustomization podinfo-oci \
--source=OCIRepository/podinfo-oci \
--path="./kustomize" \
--prune=true \
--interval=5m \
--target-namespace=default \
--wait=true \
--health-check-timeout=3m
/tmp/flux reconcile source oci podinfo-oci
/tmp/flux suspend source oci podinfo-oci
/tmp/flux get sources oci
/tmp/flux resume source oci podinfo-oci
/tmp/flux export source oci podinfo-oci
/tmp/flux delete ks podinfo-oci --silent
/tmp/flux delete source oci podinfo-oci --silent
- name: flux create tenant
run: |
/tmp/flux create tenant dev-team --with-namespace=apps
......
VERSION?=$(shell grep 'VERSION' cmd/flux/main.go | awk '{ print $$4 }' | head -n 1 | tr -d '"')
DEV_VERSION?=0.0.0-$(shell git rev-parse --abbrev-ref HEAD)-$(shell git rev-parse --short HEAD)-$(shell date +%s)
EMBEDDED_MANIFESTS_TARGET=cmd/flux/.manifests.done
TEST_KUBECONFIG?=/tmp/flux-e2e-test-kubeconfig
# Architecture to use envtest with
......@@ -55,6 +56,9 @@ $(EMBEDDED_MANIFESTS_TARGET): $(call rwildcard,manifests/,*.yaml *.json)
build: $(EMBEDDED_MANIFESTS_TARGET)
CGO_ENABLED=0 go build -ldflags="-s -w -X main.VERSION=$(VERSION)" -o ./bin/flux ./cmd/flux
build-dev: $(EMBEDDED_MANIFESTS_TARGET)
CGO_ENABLED=0 go build -ldflags="-s -w -X main.VERSION=$(DEV_VERSION)" -o ./bin/flux ./cmd/flux
.PHONY: install
install:
CGO_ENABLED=0 go install ./cmd/flux
......
......@@ -32,7 +32,7 @@ You can download a specific version with:
- name: Setup Flux CLI
uses: fluxcd/flux2/action@main
with:
version: 0.8.0
version: 0.32.0
```
### Automate Flux updates
......@@ -74,6 +74,92 @@ jobs:
${{ steps.update.outputs.flux_version }}
```
### Push Kubernetes manifests to container registries
Example workflow for publishing Kubernetes manifests bundled as OCI artifacts to GitHub Container Registry:
```yaml
name: push-artifact-staging
on:
push:
branches:
- 'main'
permissions:
packages: write # needed for ghcr.io access
env:
OCI_REPO: "oci://ghcr.io/my-org/manifests/${{ github.event.repository.name }}"
jobs:
kubernetes:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Flux CLI
uses: fluxcd/flux2/action@main
- name: Login to GHCR
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Generate manifests
run: |
kustomize build ./manifests/staging > ./deploy/app.yaml
- name: Push manifests
run: |
flux push artifact $OCI_REPO:$(git rev-parse --short HEAD) \
--path="./deploy" \
--source="$(git config --get remote.origin.url)" \
--revision="$(git branch --show-current)/$(git rev-parse HEAD)"
- name: Deploy manifests to staging
run: |
flux tag artifact $OCI_REPO:$(git rev-parse --short HEAD) --tag staging
```
Example workflow for publishing Kubernetes manifests bundled as OCI artifacts to Docker Hub:
```yaml
name: push-artifact-production
on:
push:
tags:
- '*'
env:
OCI_REPO: "oci://docker.io/my-org/app-config"
jobs:
kubernetes:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Flux CLI
uses: fluxcd/flux2/action@main
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Generate manifests
run: |
kustomize build ./manifests/production > ./deploy/app.yaml
- name: Push manifests
run: |
flux push artifact $OCI_REPO:$(git tag --points-at HEAD) \
--path="./deploy" \
--source="$(git config --get remote.origin.url)" \
--revision="$(git tag --points-at HEAD)/$(git rev-parse HEAD)"
- name: Deploy manifests to production
run: |
flux tag artifact $OCI_REPO:$(git tag --points-at HEAD) --tag production
```
### End-to-end testing
Example workflow for running Flux in Kubernetes Kind:
......
/*
Copyright 2022 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"fmt"
"os"
"github.com/spf13/cobra"
oci "github.com/fluxcd/pkg/oci/client"
)
var buildArtifactCmd = &cobra.Command{
Use: "artifact",
Short: "Build artifact",
Long: `The build artifact command creates a tgz file with the manifests from the given directory.`,
Example: ` # Build the given manifests directory into an artifact
flux build artifact --path ./path/to/local/manifests --output ./path/to/artifact.tgz
# List the files bundled in the artifact
tar -ztvf ./path/to/artifact.tgz
`,
RunE: buildArtifactCmdRun,
}
type buildArtifactFlags struct {
output string
path string
}
var buildArtifactArgs buildArtifactFlags
func init() {
buildArtifactCmd.Flags().StringVar(&buildArtifactArgs.path, "path", "", "Path to the directory where the Kubernetes manifests are located.")
buildArtifactCmd.Flags().StringVarP(&buildArtifactArgs.output, "output", "o", "artifact.tgz", "Path to where the artifact tgz file should be written.")
buildCmd.AddCommand(buildArtifactCmd)
}
func buildArtifactCmdRun(cmd *cobra.Command, args []string) error {
if buildArtifactArgs.path == "" {
return fmt.Errorf("invalid path %q", buildArtifactArgs.path)
}
if fs, err := os.Stat(buildArtifactArgs.path); err != nil || !fs.IsDir() {
return fmt.Errorf("invalid path '%s', must point to an existing directory", buildArtifactArgs.path)
}
logger.Actionf("building artifact from %s", buildArtifactArgs.path)
ociClient := oci.NewLocalClient()
if err := ociClient.Build(buildArtifactArgs.output, buildArtifactArgs.path); err != nil {
return fmt.Errorf("bulding artifact failed, error: %w", err)
}
logger.Successf("artifact created at %s", buildArtifactArgs.output)
return nil
}
......@@ -68,6 +68,13 @@ var createKsCmd = &cobra.Command{
--prune=true \
--interval=5m
# Create a Kustomization resource that references an OCIRepository
flux create kustomization podinfo \
--source=OCIRepository/podinfo \
--target-namespace=default \
--prune=true \
--interval=5m
# Create a Kustomization resource that references a Bucket
flux create kustomization secrets \
--source=Bucket/secrets \
......
/*
Copyright 2022 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
......
/*
Copyright 2022 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"fmt"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
"github.com/google/go-containerregistry/pkg/name"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/yaml"
)
var createSecretOCICmd = &cobra.Command{
Use: "oci [name]",
Short: "Create or update a Kubernetes image pull secret",
Long: `The create secret oci command generates a Kubernetes secret that can be used for OCIRepository authentication`,
Example: ` # Create an OCI authentication secret on disk and encrypt it with Mozilla SOPS
flux create secret oci podinfo-auth \
--url=ghcr.io \
--username=username \
--password=password \
--export > repo-auth.yaml
sops --encrypt --encrypted-regex '^(data|stringData)$' \
--in-place repo-auth.yaml
`,
RunE: createSecretOCICmdRun,
}
type secretOCIFlags struct {
url string
password string
username string
}
var secretOCIArgs = secretOCIFlags{}
func init() {
createSecretOCICmd.Flags().StringVar(&secretOCIArgs.url, "url", "", "oci repository address e.g ghcr.io/stefanprodan/charts")
createSecretOCICmd.Flags().StringVarP(&secretOCIArgs.username, "username", "u", "", "basic authentication username")
createSecretOCICmd.Flags().StringVarP(&secretOCIArgs.password, "password", "p", "", "basic authentication password")
createSecretCmd.AddCommand(createSecretOCICmd)
}
func createSecretOCICmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("name is required")
}
secretName := args[0]
if secretOCIArgs.url == "" {
return fmt.Errorf("--url is required")
}
if secretOCIArgs.username == "" {
return fmt.Errorf("--username is required")
}
if secretOCIArgs.password == "" {
return fmt.Errorf("--password is required")
}
if _, err := name.ParseReference(secretOCIArgs.url); err != nil {
return fmt.Errorf("error parsing url: '%s'", err)
}
opts := sourcesecret.Options{
Name: secretName,
Namespace: *kubeconfigArgs.Namespace,
Registry: secretOCIArgs.url,
Password: secretOCIArgs.password,
Username: secretOCIArgs.username,
}
secret, err := sourcesecret.Generate(opts)
if err != nil {
return err
}
if createArgs.export {
rootCmd.Println(secret.Content)
return nil
}
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
if err != nil {
return err
}
var s corev1.Secret
if err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil {
return err
}
if err := upsertSecret(ctx, kubeClient, s); err != nil {
return err
}
logger.Actionf("oci secret '%s' created in '%s' namespace", secretName, *kubeconfigArgs.Namespace)
return nil
}
/*
Copyright 2022 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"testing"
)
func TestCreateSecretOCI(t *testing.T) {
tests := []struct {
name string
args string
assert assertFunc
}{
{
args: "create secret oci",
assert: assertError("name is required"),
},
{
args: "create secret oci ghcr",
assert: assertError("--url is required"),
},
{
args: "create secret oci ghcr --namespace=my-namespace --url ghcr.io --username stefanprodan --password=password --export",
assert: assertGoldenFile("testdata/create_secret/oci/create-secret.yaml"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := cmdTestCase{
args: tt.args,
assert: tt.assert,
}
cmd.runTestCmd(t)
})
}
}
/*
Copyright 2022 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"fmt"
"strings"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/conditions"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/internal/utils"
)
var createSourceOCIRepositoryCmd = &cobra.Command{
Use: "oci [name]",
Short: "Create or update an OCIRepository",
Long: `The create source oci command generates an OCIRepository resource and waits for it to be ready.`,
Example: ` # Create an OCIRepository for a public container image
flux create source oci podinfo \
--url=oci://ghcr.io/stefanprodan/manifests/podinfo \
--tag=6.1.6 \
--interval=10m
`,
RunE: createSourceOCIRepositoryCmdRun,
}
type sourceOCIRepositoryFlags struct {
url string
tag string
semver string
digest string
secretRef string
serviceAccount string
certSecretRef string
ignorePaths []string
provider flags.SourceOCIProvider
}
var sourceOCIRepositoryArgs = newSourceOCIFlags()
func newSourceOCIFlags() sourceOCIRepositoryFlags {
return sourceOCIRepositoryFlags{
provider: flags.SourceOCIProvider(sourcev1.GenericOCIProvider),
}
}
func init() {
createSourceOCIRepositoryCmd.Flags().Var(&sourceOCIRepositoryArgs.provider, "provider", sourceOCIRepositoryArgs.provider.Description())
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.url, "url", "", "the OCI repository URL")
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.tag, "tag", "", "the OCI artifact tag")
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.semver, "tag-semver", "", "the OCI artifact tag semver range")
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.digest, "digest", "", "the OCI artifact digest")
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.secretRef, "secret-ref", "", "the name of the Kubernetes image pull secret (type 'kubernetes.io/dockerconfigjson')")
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.serviceAccount, "service-account", "", "the name of the Kubernetes service account that refers to an image pull secret")
createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.certSecretRef, "cert-ref", "", "the name of a secret to use for TLS certificates")
createSourceOCIRepositoryCmd.Flags().StringSliceVar(&sourceOCIRepositoryArgs.ignorePaths, "ignore-paths", nil, "set paths to ignore resources (can specify multiple paths with commas: path1,path2)")
createSourceCmd.AddCommand(createSourceOCIRepositoryCmd)
}
func createSourceOCIRepositoryCmdRun(cmd *cobra.Command, args []string) error {
name := args[0]
if sourceOCIRepositoryArgs.url == "" {
return fmt.Errorf("url is required")
}
if sourceOCIRepositoryArgs.semver == "" && sourceOCIRepositoryArgs.tag == "" && sourceOCIRepositoryArgs.digest == "" {
return fmt.Errorf("--tag, --tag-semver or --digest is required")
}
sourceLabels, err := parseLabels()
if err != nil {
return err
}
var ignorePaths *string
if len(sourceOCIRepositoryArgs.ignorePaths) > 0 {
ignorePathsStr := strings.Join(sourceOCIRepositoryArgs.ignorePaths, "\n")
ignorePaths = &ignorePathsStr
}
repository := &sourcev1.OCIRepository{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: *kubeconfigArgs.Namespace,
Labels: sourceLabels,
},
Spec: sourcev1.OCIRepositorySpec{
Provider: sourceOCIRepositoryArgs.provider.String(),
URL: sourceOCIRepositoryArgs.url,
Interval: metav1.Duration{
Duration: createArgs.interval,
},
Reference: &sourcev1.OCIRepositoryRef{},
Ignore: ignorePaths,
},
}
if digest := sourceOCIRepositoryArgs.digest; digest != "" {
repository.Spec.Reference.Digest = digest
}
if semver := sourceOCIRepositoryArgs.semver; semver != "" {
repository.Spec.Reference.SemVer = semver
}
if tag := sourceOCIRepositoryArgs.tag; tag != "" {
repository.Spec.Reference.Tag = tag
}
if createSourceArgs.fetchTimeout > 0 {
repository.Spec.Timeout = &metav1.Duration{Duration: createSourceArgs.fetchTimeout}
}
if saName := sourceOCIRepositoryArgs.serviceAccount; saName != "" {
repository.Spec.ServiceAccountName = saName
}
if secretName := sourceOCIRepositoryArgs.secretRef; secretName != "" {
repository.Spec.SecretRef = &meta.LocalObjectReference{
Name: secretName,
}
}
if secretName := sourceOCIRepositoryArgs.certSecretRef; secretName != "" {
repository.Spec.CertSecretRef = &meta.LocalObjectReference{
Name: secretName,
}
}
if createArgs.export {
return printExport(exportOCIRepository(repository))
}
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
if err != nil {
return err
}
logger.Actionf("applying OCIRepository")
namespacedName, err := upsertOCIRepository(ctx, kubeClient, repository)
if err != nil {
return err
}
logger.Waitingf("waiting for OCIRepository reconciliation")
if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
isOCIRepositoryReady(ctx, kubeClient, namespacedName, repository)); err != nil {
return err
}
logger.Successf("OCIRepository reconciliation completed")
if repository.Status.Artifact == nil {
return fmt.Errorf("no artifact was found")
}
logger.Successf("fetched revision: %s", repository.Status.Artifact.Revision)
return nil
}
func upsertOCIRepository(ctx context.Context, kubeClient client.Client,
ociRepository *sourcev1.OCIRepository) (types.NamespacedName, error) {
namespacedName := types.NamespacedName{
Namespace: ociRepository.GetNamespace(),
Name: ociRepository.GetName(),
}
var existing sourcev1.OCIRepository
err := kubeClient.Get(ctx, namespacedName, &existing)
if err != nil {
if errors.IsNotFound(err) {
if err := kubeClient.Create(ctx, ociRepository); err != nil {
return namespacedName, err
} else {
logger.Successf("OCIRepository created")
return namespacedName, nil
}
}
return namespacedName, err
}
existing.Labels = ociRepository.Labels
existing.Spec = ociRepository.Spec
if err := kubeClient.Update(ctx, &existing); err != nil {
return namespacedName, err
}
ociRepository = &existing
logger.Successf("OCIRepository updated")
return namespacedName, nil
}
func isOCIRepositoryReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, ociRepository *sourcev1.OCIRepository) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, ociRepository)
if err != nil {
return false, err
}
if c := conditions.Get(ociRepository, meta.ReadyCondition); c != nil {
// Confirm the Ready condition we are observing is for the
// current generation
if c.ObservedGeneration != ociRepository.GetGeneration() {
return false, nil
}
// Further check the Status
switch c.Status {
case metav1.ConditionTrue:
return true, nil
case metav1.ConditionFalse:
return false, fmt.Errorf(c.Message)
}
}
return false, nil
}
}
/*
Copyright 2022 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"testing"
)
func TestCreateSourceOCI(t *testing.T) {
tests := []struct {
name string
args string
assertFunc assertFunc
}{
{
name: "NoArgs",
args: "create source oci",
assertFunc: assertError("name is required"),
},
{
name: "NoURL",
args: "create source oci podinfo",
assertFunc: assertError("url is required"),
},
{
name: "export manifest",
args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.1.6 --interval 10m --export",
assertFunc: assertGoldenFile("./testdata/oci/export.golden"),
},
{
name: "export manifest with secret",
args: "create source oci podinfo --url=oci://ghcr.io/stefanprodan/manifests/podinfo --tag=6.1.6 --interval 10m --secret-ref=creds --export",
assertFunc: assertGoldenFile("./testdata/oci/export_with_secret.golden"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := cmdTestCase{
args: tt.args,
assert: tt.assertFunc,
}
cmd.runTestCmd(t)
})
}
}
/*
Copyright 2022 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"github.com/spf13/cobra"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
)
var deleteSourceOCIRepositoryCmd = &cobra.Command{
Use: "oci [name]",
Short: "Delete an OCIRepository source",
Long: "The delete source oci command deletes the given OCIRepository from the cluster.",
Example: ` # Delete an OCIRepository
flux delete source oci podinfo`,
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)),
RunE: deleteCommand{
apiType: ociRepositoryType,
object: universalAdapter{&sourcev1.OCIRepository{}},
}.run,
}
func init() {
deleteSourceCmd.AddCommand(deleteSourceOCIRepositoryCmd)
}
/*
Copyright 2022 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
)
var exportSourceOCIRepositoryCmd = &cobra.Command{
Use: "oci [name]",
Short: "Export OCIRepository sources in YAML format",
Long: "The export source oci command exports one or all OCIRepository sources in YAML format.",
Example: ` # Export all OCIRepository sources
flux export source oci --all > sources.yaml
# Export a OCIRepository including the static credentials
flux export source oci my-app --with-credentials > source.yaml`,
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)),
RunE: exportWithSecretCommand{
list: ociRepositoryListAdapter{&sourcev1.OCIRepositoryList{}},
object: ociRepositoryAdapter{&sourcev1.OCIRepository{}},
}.run,
}
func init() {
exportSourceCmd.AddCommand(exportSourceOCIRepositoryCmd)
}
func exportOCIRepository(source *sourcev1.OCIRepository) interface{} {
gvk := sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)
export := sourcev1.OCIRepository{
TypeMeta: metav1.TypeMeta{
Kind: gvk.Kind,
APIVersion: gvk.GroupVersion().String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: source.Name,
Namespace: source.Namespace,
Labels: source.Labels,
Annotations: source.Annotations,
},
Spec: source.Spec,
}
return export
}
func getOCIRepositorySecret(source *sourcev1.OCIRepository) *types.NamespacedName {
if source.Spec.SecretRef != nil {
namespacedName := types.NamespacedName{
Namespace: source.Namespace,
Name: source.Spec.SecretRef.Name,
}
return &namespacedName
}
return nil
}
func (ex ociRepositoryAdapter) secret() *types.NamespacedName {
return getOCIRepositorySecret(ex.OCIRepository)
}
func (ex ociRepositoryListAdapter) secretItem(i int) *types.NamespacedName {
return getOCIRepositorySecret(&ex.OCIRepositoryList.Items[i])
}
func (ex ociRepositoryAdapter) export() interface{} {
return exportOCIRepository(ex.OCIRepository)
}
func (ex ociRepositoryListAdapter) exportItem(i int) interface{} {
return exportOCIRepository(&ex.OCIRepositoryList.Items[i])
}
......@@ -40,6 +40,10 @@ var getSourceAllCmd = &cobra.Command{
}
var allSourceCmd = []getCommand{
{
apiType: ociRepositoryType,
list: &ociRepositoryListAdapter{&sourcev1.OCIRepositoryList{}},
},
{
apiType: bucketType,
list: &bucketListAdapter{&sourcev1.BucketList{}},
......
/*
Copyright 2022 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"fmt"
"strconv"
"strings"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
)
var getSourceOCIRepositoryCmd = &cobra.Command{
Use: "oci",
Short: "Get OCIRepository status",
Long: "The get sources oci command prints the status of the OCIRepository sources.",
Example: ` # List all OCIRepositories and their status
flux get sources oci
# List OCIRepositories from all namespaces
flux get sources oci --all-namespaces`,
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)),
RunE: func(cmd *cobra.Command, args []string) error {
get := getCommand{
apiType: ociRepositoryType,
list: &ociRepositoryListAdapter{&sourcev1.OCIRepositoryList{}},
funcMap: make(typeMap),
}
err := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {
o, ok := obj.(*sourcev1.OCIRepository)
if !ok {
return nil, fmt.Errorf("impossible to cast type %#v to OCIRepository", obj)
}
sink := &ociRepositoryListAdapter{&sourcev1.OCIRepositoryList{
Items: []sourcev1.OCIRepository{
*o,
}}}
return sink, nil
})
if err != nil {
return err
}
if err := get.run(cmd, args); err != nil {
return err
}
return nil
},
}
func init() {
getSourceCmd.AddCommand(getSourceOCIRepositoryCmd)
}
func (a *ociRepositoryListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
item := a.Items[i]
var revision string
if item.GetArtifact() != nil {
revision = item.GetArtifact().Revision
}
status, msg := statusAndMessage(item.Status.Conditions)
return append(nameColumns(&item, includeNamespace, includeKind),
revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
}
func (a ociRepositoryListAdapter) headers(includeNamespace bool) []string {
headers := []string{"Name", "Revision", "Suspended", "Ready", "Message"}
if includeNamespace {
headers = append([]string{"Namespace"}, headers...)
}
return headers
}
func (a ociRepositoryListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {
item := a.Items[i]
return statusMatches(conditionType, conditionStatus, item.Status.Conditions)
}
//go:build e2e
// +build e2e
/*
Copyright 2022 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import "testing"
......
/*
Copyright 2022 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import "testing"
func TestInstall(t *testing.T) {
// The pointer to kubeconfigArgs.Namespace is shared across
// the tests. When a new value is set, it will linger and
// impact subsequent tests.
// Given that this test uses an invalid namespace, it ensures
// to restore whatever value it had previously.
currentNamespace := *kubeconfigArgs.Namespace
defer func() {
*kubeconfigArgs.Namespace = currentNamespace
}()
tests := []struct {
name string
args string
assert assertFunc
}{
{
name: "invalid namespace",
args: "install --namespace='@#[]'",
assert: assertError("namespace must be a valid DNS label: \"@#[]\""),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := cmdTestCase{
args: tt.args,
assert: tt.assert,
}
cmd.runTestCmd(t)
})
}
}
/*
Copyright 2022 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"github.com/spf13/cobra"
)
var listCmd = &cobra.Command{
Use: "list",
Short: "List artifacts",
Long: "The list command is used for printing the OCI artifacts metadata.",
}
func init() {
rootCmd.AddCommand(listCmd)
}
/*
Copyright 2022 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"fmt"
"github.com/spf13/cobra"
oci "github.com/fluxcd/pkg/oci/client"
"github.com/fluxcd/flux2/pkg/printers"
)
var listArtifactsCmd = &cobra.Command{
Use: "artifacts",
Short: "list artifacts",
Long: `The list command fetches the tags and their metadata from a remote OCI repository.
The command uses the credentials from '~/.docker/config.json'.`,
Example: ` # List the artifacts stored in an OCI repository
flux list artifact oci://ghcr.io/org/config/app
`,
RunE: listArtifactsCmdRun,
}
func init() {
listCmd.AddCommand(listArtifactsCmd)
}
func listArtifactsCmdRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("artifact repository URL is required")
}
ociURL := args[0]
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()
ociClient := oci.NewLocalClient()
url, err := oci.ParseArtifactURL(ociURL)
if err != nil {
return err
}
metas, err := ociClient.List(ctx, url)
if err != nil {
return err
}
var rows [][]string
for _, meta := range metas {
rows = append(rows, []string{meta.URL, meta.Digest, meta.Source, meta.Revision})
}
err = printers.TablePrinter([]string{"artifact", "digest", "source", "revision"}).Print(cmd.OutOrStdout(), rows)
if err != nil {
return err
}
return nil
}