Skip to content
Open

apply #691

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 23 additions & 9 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: Nais CLI
on:
pull_request:
types: [opened, reopened, synchronize]
types: [opened, reopened, synchronize, labeled]
push:
branches: [main]
paths-ignore: ["**.md"]
Expand All @@ -10,6 +10,8 @@ concurrency:
cancel-in-progress: true
permissions:
contents: read
env:
PRE_RELEASE: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'pre-release') && 'true' || 'false' }}
jobs:
release-info:
runs-on: ubuntu-latest
Expand All @@ -19,6 +21,7 @@ jobs:
outputs:
version: ${{ steps.release-info.outputs.version }}
changelog: ${{ steps.release-info.outputs.changelog }}
pre_release: ${{ steps.release-info.outputs.pre_release }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # ratchet:actions/checkout@v6
with:
Expand All @@ -29,6 +32,8 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
IS_FORK: ${{ github.event.pull_request.head.repo.full_name != github.repository }}
PRE_RELEASE: ${{ env.PRE_RELEASE }}
PR_NUMBER: ${{ github.event.pull_request.number }}
checks:
strategy:
matrix:
Expand Down Expand Up @@ -97,7 +102,13 @@ jobs:
release-github:
permissions:
contents: write
if: github.ref == 'refs/heads/main' && needs.release-info.outputs.changelog != '' && needs.release-info.outputs.version != ''
if: >-
needs.release-info.outputs.changelog != '' &&
needs.release-info.outputs.version != '' &&
(
(github.ref == 'refs/heads/main' && github.actor != 'dependabot[bot]') ||
needs.release-info.outputs.pre_release == 'true'
)
runs-on: ubuntu-latest
needs: [release-info, branch-protection-checkpoint]
steps:
Expand All @@ -115,17 +126,20 @@ jobs:
id: release
with:
tag_name: v${{ needs.release-info.outputs.version }}
target_commitish: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
body: ${{ needs.release-info.outputs.changelog }}
prerelease: false
prerelease: ${{ env.PRE_RELEASE == 'true' }}
files: ./release_artifacts/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- env:
- if: env.PRE_RELEASE != 'true'
env:
VERSION: ${{ needs.release-info.outputs.version }}
run: |
echo '${{ steps.release.outputs.assets }}' > assets.json
mise run ci:prepare-template-vars ./release_artifacts/checksums.txt ./assets.json -v > template.vars
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # ratchet:actions/upload-artifact@v5
- if: env.PRE_RELEASE != 'true'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # ratchet:actions/upload-artifact@v5
with:
name: template-vars
path: ./template.vars
Expand All @@ -134,8 +148,8 @@ jobs:
echo "## :rocket: Release v${{ needs.release-info.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo "A new release is available over at https://github.com/${{ github.repository }}/releases/tag/v${{ needs.release-info.outputs.version }}." >> $GITHUB_STEP_SUMMARY
release-gar:
if: github.ref == 'refs/heads/main'
needs: [release-github]
if: github.ref == 'refs/heads/main' && needs.release-info.outputs.pre_release != 'true'
needs: [release-info, release-github]
runs-on: ubuntu-latest
permissions:
contents: read
Expand All @@ -160,8 +174,8 @@ jobs:
gcloud --project nais-io artifacts apt upload nais-ppa --quiet --source nais-cli_arm64.deb --location europe-north1
gcloud --project nais-io artifacts apt upload nais-ppa --quiet --source nais-cli_amd64.deb --location europe-north1
release-external-repos:
if: github.ref == 'refs/heads/main'
needs: [release-github]
if: github.ref == 'refs/heads/main' && needs.release-info.outputs.pre_release != 'true'
needs: [release-info, release-github]
runs-on: ubuntu-latest
strategy:
fail-fast: false
Expand Down
185 changes: 14 additions & 171 deletions internal/apply/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,10 @@ import (
"strings"

"github.com/nais/cli/internal/apply/command/flag"
"github.com/nais/cli/internal/naisapi/gql"
"github.com/nais/cli/internal/opensearch"
"github.com/nais/cli/internal/valkey"
"github.com/nais/cli/internal/naisapi"
"github.com/nais/naistrix"
pgratorv1 "github.com/nais/pgrator/pkg/api/v1"
"gopkg.in/yaml.v3"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
)

var (
scheme = runtime.NewScheme()
_ = pgratorv1.AddToScheme(scheme)
codecs = serializer.NewCodecFactory(scheme)
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

func Run(ctx context.Context, environment, filePath string, flags *flag.Apply, out *naistrix.OutputWriter) error {
Expand All @@ -34,57 +24,20 @@ func Run(ctx context.Context, environment, filePath string, flags *flag.Apply, o
}

for _, m := range manifests {
switch obj := m.(type) {
case *pgratorv1.Valkey:
if obj.Namespace != "" {
out.Warnf("Valkey %q has namespace %q set — namespace is ignored by nais apply.\n", obj.Name, obj.Namespace)
}
metadata := valkey.Metadata{
Name: obj.Name,
EnvironmentName: environment,
TeamSlug: flags.Team,
}
v, err := valkeyFromCRD(obj)
if err != nil {
return fmt.Errorf("valkey %q: %w", obj.Name, err)
}
if err := valkey.Upsert(ctx, metadata, v); err != nil {
return fmt.Errorf("failed to apply Valkey %q: %w", obj.Name, err)
}
if flags.IsVerbose() {
out.Printf("Applied Valkey %q to environment %q for team %q\n", obj.Name, environment, flags.Team)
}

case *pgratorv1.OpenSearch:
if obj.Namespace != "" {
out.Warnf("OpenSearch %q has namespace %q set — namespace is ignored by nais apply.\n", obj.Name, obj.Namespace)
}
metadata := opensearch.Metadata{
Name: obj.Name,
EnvironmentName: environment,
TeamSlug: flags.Team,
}
o, err := openSearchFromCRD(obj)
if err != nil {
return fmt.Errorf("openSearch %q: %w", obj.Name, err)
}
if err := opensearch.Upsert(ctx, metadata, o); err != nil {
return fmt.Errorf("failed to apply OpenSearch %q: %w", obj.Name, err)
}
if flags.IsVerbose() {
out.Printf("Applied OpenSearch %q to environment %q for team %q\n", obj.Name, environment, flags.Team)
}

default:
return fmt.Errorf("unsupported resource type %T", m)
if m.GetNamespace() != "" {
out.Warnf("The %v %q has namespace %q set — namespace is ignored by nais apply.\n", m.GetKind(), m.GetName(), m.GetNamespace())
}
}

if err := naisapi.ApplyManifests(ctx, flags.Team, string(flags.Environment), manifests); err != nil {
return fmt.Errorf("failed to apply manifests: %w", err)
}

return nil
}

// loadManifests reads all YAML documents from filePath and decodes them as CRD objects.
func loadManifests(filePath string) ([]runtime.Object, error) {
func loadManifests(filePath string) ([]unstructured.Unstructured, error) {
if filePath == "" {
return nil, fmt.Errorf("file path cannot be empty")
}
Expand All @@ -101,133 +54,23 @@ func loadManifests(filePath string) ([]runtime.Object, error) {
return nil, fmt.Errorf("failed to read file %s: %w", filePath, err)
}

var objects []runtime.Object
var objects []unstructured.Unstructured
decoder := yaml.NewDecoder(bytes.NewReader(data))
deserializer := codecs.UniversalDeserializer()

for {
var raw map[string]any
if err := decoder.Decode(&raw); err != nil {
var raw unstructured.Unstructured
if err := decoder.Decode(&raw.Object); err != nil {
if errors.Is(err, io.EOF) {
break
}
return nil, fmt.Errorf("failed to decode YAML from %s: %w", filePath, err)
}
if len(raw) == 0 {
if len(raw.Object) == 0 {
continue
}

// Re-encode the single document back to YAML bytes for the k8s deserializer.
docBytes, err := yaml.Marshal(raw)
if err != nil {
return nil, fmt.Errorf("failed to re-encode YAML document from %s: %w", filePath, err)
}

obj, _, err := deserializer.Decode(docBytes, nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to decode manifest from %s: %w", filePath, err)
}

objects = append(objects, obj)
objects = append(objects, raw)
}

return objects, nil
}

var valkeyTierMap = map[pgratorv1.ValkeyTier]gql.ValkeyTier{
pgratorv1.ValkeyTierSingleNode: gql.ValkeyTierSingleNode,
pgratorv1.ValkeyTierHighAvailability: gql.ValkeyTierHighAvailability,
}

var valkeyMemoryMap = map[pgratorv1.ValkeyMemory]gql.ValkeyMemory{
pgratorv1.ValkeyMemory1GB: gql.ValkeyMemoryGb1,
pgratorv1.ValkeyMemory4GB: gql.ValkeyMemoryGb4,
pgratorv1.ValkeyMemory8GB: gql.ValkeyMemoryGb8,
pgratorv1.ValkeyMemory14GB: gql.ValkeyMemoryGb14,
pgratorv1.ValkeyMemory28GB: gql.ValkeyMemoryGb28,
pgratorv1.ValkeyMemory56GB: gql.ValkeyMemoryGb56,
pgratorv1.ValkeyMemory112GB: gql.ValkeyMemoryGb112,
pgratorv1.ValkeyMemory200GB: gql.ValkeyMemoryGb200,
}

var valkeyMaxMemoryPolicyMap = map[pgratorv1.ValkeyMaxMemoryPolicy]gql.ValkeyMaxMemoryPolicy{
pgratorv1.ValkeyMaxMemoryPolicyAllkeysLFU: gql.ValkeyMaxMemoryPolicyAllkeysLfu,
pgratorv1.ValkeyMaxMemoryPolicyAllkeysLRU: gql.ValkeyMaxMemoryPolicyAllkeysLru,
pgratorv1.ValkeyMaxMemoryPolicyAllkeysRandom: gql.ValkeyMaxMemoryPolicyAllkeysRandom,
pgratorv1.ValkeyMaxMemoryPolicyNoEviction: gql.ValkeyMaxMemoryPolicyNoEviction,
pgratorv1.ValkeyMaxMemoryPolicyVolatileLFU: gql.ValkeyMaxMemoryPolicyVolatileLfu,
pgratorv1.ValkeyMaxMemoryPolicyVolatileLRU: gql.ValkeyMaxMemoryPolicyVolatileLru,
pgratorv1.ValkeyMaxMemoryPolicyVolatileRandom: gql.ValkeyMaxMemoryPolicyVolatileRandom,
pgratorv1.ValkeyMaxMemoryPolicyVolatileTTL: gql.ValkeyMaxMemoryPolicyVolatileTtl,
}

func valkeyFromCRD(obj *pgratorv1.Valkey) (*valkey.Valkey, error) {
tier, ok := valkeyTierMap[obj.Spec.Tier]
if !ok {
return nil, fmt.Errorf("unsupported tier %q", obj.Spec.Tier)
}

mem, ok := valkeyMemoryMap[obj.Spec.Memory]
if !ok {
return nil, fmt.Errorf("unsupported memory %q", obj.Spec.Memory)
}

var maxMemPolicy gql.ValkeyMaxMemoryPolicy
if obj.Spec.MaxMemoryPolicy != "" {
maxMemPolicy, ok = valkeyMaxMemoryPolicyMap[obj.Spec.MaxMemoryPolicy]
if !ok {
return nil, fmt.Errorf("unsupported maxMemoryPolicy %q", obj.Spec.MaxMemoryPolicy)
}
}

return &valkey.Valkey{
Tier: tier,
Memory: mem,
MaxMemoryPolicy: maxMemPolicy,
}, nil
}

var openSearchTierMap = map[pgratorv1.OpenSearchTier]gql.OpenSearchTier{
pgratorv1.OpenSearchTierSingleNode: gql.OpenSearchTierSingleNode,
pgratorv1.OpenSearchTierHighAvailability: gql.OpenSearchTierHighAvailability,
}

var openSearchMemoryMap = map[pgratorv1.OpenSearchMemory]gql.OpenSearchMemory{
pgratorv1.OpenSearchMemory2GB: gql.OpenSearchMemoryGb2,
pgratorv1.OpenSearchMemory4GB: gql.OpenSearchMemoryGb4,
pgratorv1.OpenSearchMemory8GB: gql.OpenSearchMemoryGb8,
pgratorv1.OpenSearchMemory16GB: gql.OpenSearchMemoryGb16,
pgratorv1.OpenSearchMemory32GB: gql.OpenSearchMemoryGb32,
pgratorv1.OpenSearchMemory64GB: gql.OpenSearchMemoryGb64,
}

var openSearchVersionMap = map[pgratorv1.OpenSearchVersion]gql.OpenSearchMajorVersion{
pgratorv1.OpenSearchVersionV1: gql.OpenSearchMajorVersionV1,
pgratorv1.OpenSearchVersionV2: gql.OpenSearchMajorVersionV2,
pgratorv1.OpenSearchVersionV2_19: gql.OpenSearchMajorVersionV219,
pgratorv1.OpenSearchVersionV3_3: gql.OpenSearchMajorVersionV33,
}

func openSearchFromCRD(obj *pgratorv1.OpenSearch) (*opensearch.OpenSearch, error) {
tier, ok := openSearchTierMap[obj.Spec.Tier]
if !ok {
return nil, fmt.Errorf("unsupported tier %q", obj.Spec.Tier)
}

mem, ok := openSearchMemoryMap[obj.Spec.Memory]
if !ok {
return nil, fmt.Errorf("unsupported memory %q", obj.Spec.Memory)
}

version, ok := openSearchVersionMap[obj.Spec.Version]
if !ok {
return nil, fmt.Errorf("unsupported version %q", obj.Spec.Version)
}

return &opensearch.OpenSearch{
Tier: tier,
Memory: mem,
Version: version,
StorageGB: obj.Spec.StorageGB,
}, nil
}
Loading
Loading