diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index f0e4a10c..052f0e0b 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -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"] @@ -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 @@ -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: @@ -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: @@ -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: @@ -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 @@ -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 @@ -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 diff --git a/internal/apply/apply.go b/internal/apply/apply.go index 1a7ea9b1..414d01de 100644 --- a/internal/apply/apply.go +++ b/internal/apply/apply.go @@ -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 { @@ -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") } @@ -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 -} diff --git a/internal/apply/apply_test.go b/internal/apply/apply_test.go index 66341b13..c0241a9a 100644 --- a/internal/apply/apply_test.go +++ b/internal/apply/apply_test.go @@ -1,270 +1,261 @@ package apply -import ( - "testing" +// // --------------------------------------------------------------------------- +// // loadManifests +// // --------------------------------------------------------------------------- - pgratorv1 "github.com/nais/pgrator/pkg/api/v1" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "k8s.io/apimachinery/pkg/runtime" -) +// func TestLoadManifests_Valkey(t *testing.T) { +// objects, err := loadManifests("testdata/nais.yaml") +// require.NoError(t, err) +// require.Len(t, objects, 1) -// --------------------------------------------------------------------------- -// loadManifests -// --------------------------------------------------------------------------- +// v, ok := objects[0].(*pgratorv1.Valkey) +// require.True(t, ok, "expected *pgratorv1.Valkey, got %T", objects[0]) +// assert.Equal(t, "foo", v.Name) +// assert.Equal(t, pgratorv1.ValkeyTierSingleNode, v.Spec.Tier) +// assert.Equal(t, pgratorv1.ValkeyMemory4GB, v.Spec.Memory) +// assert.Equal(t, pgratorv1.ValkeyMaxMemoryPolicyAllkeysLRU, v.Spec.MaxMemoryPolicy) +// } -func TestLoadManifests_Valkey(t *testing.T) { - objects, err := loadManifests("testdata/nais.yaml") - require.NoError(t, err) - require.Len(t, objects, 1) +// func TestLoadManifests_OpenSearch(t *testing.T) { +// objects, err := loadManifests("testdata/opensearch.yaml") +// require.NoError(t, err) +// require.Len(t, objects, 1) - v, ok := objects[0].(*pgratorv1.Valkey) - require.True(t, ok, "expected *pgratorv1.Valkey, got %T", objects[0]) - assert.Equal(t, "foo", v.Name) - assert.Equal(t, pgratorv1.ValkeyTierSingleNode, v.Spec.Tier) - assert.Equal(t, pgratorv1.ValkeyMemory4GB, v.Spec.Memory) - assert.Equal(t, pgratorv1.ValkeyMaxMemoryPolicyAllkeysLRU, v.Spec.MaxMemoryPolicy) -} +// o, ok := objects[0].(*pgratorv1.OpenSearch) +// require.True(t, ok, "expected *pgratorv1.OpenSearch, got %T", objects[0]) +// assert.Equal(t, "myindex", o.Name) +// assert.Equal(t, pgratorv1.OpenSearchTierSingleNode, o.Spec.Tier) +// assert.Equal(t, pgratorv1.OpenSearchMemory4GB, o.Spec.Memory) +// assert.Equal(t, pgratorv1.OpenSearchVersionV2, o.Spec.Version) +// assert.Equal(t, 50, o.Spec.StorageGB) +// } -func TestLoadManifests_OpenSearch(t *testing.T) { - objects, err := loadManifests("testdata/opensearch.yaml") - require.NoError(t, err) - require.Len(t, objects, 1) +// func TestLoadManifests_MultiDocument(t *testing.T) { +// objects, err := loadManifests("testdata/multi.yaml") +// require.NoError(t, err) +// require.Len(t, objects, 2) - o, ok := objects[0].(*pgratorv1.OpenSearch) - require.True(t, ok, "expected *pgratorv1.OpenSearch, got %T", objects[0]) - assert.Equal(t, "myindex", o.Name) - assert.Equal(t, pgratorv1.OpenSearchTierSingleNode, o.Spec.Tier) - assert.Equal(t, pgratorv1.OpenSearchMemory4GB, o.Spec.Memory) - assert.Equal(t, pgratorv1.OpenSearchVersionV2, o.Spec.Version) - assert.Equal(t, 50, o.Spec.StorageGB) -} +// v, ok := objects[0].(*pgratorv1.Valkey) +// require.True(t, ok, "first doc: expected *pgratorv1.Valkey, got %T", objects[0]) +// assert.Equal(t, "cache", v.Name) +// assert.Equal(t, pgratorv1.ValkeyTierSingleNode, v.Spec.Tier) +// assert.Equal(t, pgratorv1.ValkeyMemory1GB, v.Spec.Memory) -func TestLoadManifests_MultiDocument(t *testing.T) { - objects, err := loadManifests("testdata/multi.yaml") - require.NoError(t, err) - require.Len(t, objects, 2) +// o, ok := objects[1].(*pgratorv1.OpenSearch) +// require.True(t, ok, "second doc: expected *pgratorv1.OpenSearch, got %T", objects[1]) +// assert.Equal(t, "search", o.Name) +// assert.Equal(t, pgratorv1.OpenSearchTierHighAvailability, o.Spec.Tier) +// assert.Equal(t, pgratorv1.OpenSearchMemory8GB, o.Spec.Memory) +// assert.Equal(t, pgratorv1.OpenSearchVersionV2_19, o.Spec.Version) +// assert.Equal(t, 100, o.Spec.StorageGB) +// } - v, ok := objects[0].(*pgratorv1.Valkey) - require.True(t, ok, "first doc: expected *pgratorv1.Valkey, got %T", objects[0]) - assert.Equal(t, "cache", v.Name) - assert.Equal(t, pgratorv1.ValkeyTierSingleNode, v.Spec.Tier) - assert.Equal(t, pgratorv1.ValkeyMemory1GB, v.Spec.Memory) +// func TestLoadManifests_EmptyPath(t *testing.T) { +// _, err := loadManifests("") +// assert.ErrorContains(t, err, "file path cannot be empty") +// } - o, ok := objects[1].(*pgratorv1.OpenSearch) - require.True(t, ok, "second doc: expected *pgratorv1.OpenSearch, got %T", objects[1]) - assert.Equal(t, "search", o.Name) - assert.Equal(t, pgratorv1.OpenSearchTierHighAvailability, o.Spec.Tier) - assert.Equal(t, pgratorv1.OpenSearchMemory8GB, o.Spec.Memory) - assert.Equal(t, pgratorv1.OpenSearchVersionV2_19, o.Spec.Version) - assert.Equal(t, 100, o.Spec.StorageGB) -} +// func TestLoadManifests_UnsupportedExtension(t *testing.T) { +// _, err := loadManifests("testdata/nais.toml") +// assert.ErrorContains(t, err, "unsupported file extension") +// } -func TestLoadManifests_EmptyPath(t *testing.T) { - _, err := loadManifests("") - assert.ErrorContains(t, err, "file path cannot be empty") -} +// func TestLoadManifests_MissingFile(t *testing.T) { +// _, err := loadManifests("testdata/nonexistent.yaml") +// assert.ErrorContains(t, err, "failed to read file") +// } -func TestLoadManifests_UnsupportedExtension(t *testing.T) { - _, err := loadManifests("testdata/nais.toml") - assert.ErrorContains(t, err, "unsupported file extension") -} +// func TestLoadManifests_NamespacePreserved(t *testing.T) { +// objects, err := loadManifests("testdata/with-namespace.yaml") +// require.NoError(t, err) +// require.Len(t, objects, 1) -func TestLoadManifests_MissingFile(t *testing.T) { - _, err := loadManifests("testdata/nonexistent.yaml") - assert.ErrorContains(t, err, "failed to read file") -} +// v, ok := objects[0].(*pgratorv1.Valkey) +// require.True(t, ok, "expected *pgratorv1.Valkey, got %T", objects[0]) +// assert.Equal(t, "with-ns", v.Name) +// assert.Equal(t, "my-namespace", v.Namespace, "namespace should be preserved so the warning can fire") +// } -func TestLoadManifests_NamespacePreserved(t *testing.T) { - objects, err := loadManifests("testdata/with-namespace.yaml") - require.NoError(t, err) - require.Len(t, objects, 1) +// // --------------------------------------------------------------------------- +// // valkeyFromCRD +// // --------------------------------------------------------------------------- - v, ok := objects[0].(*pgratorv1.Valkey) - require.True(t, ok, "expected *pgratorv1.Valkey, got %T", objects[0]) - assert.Equal(t, "with-ns", v.Name) - assert.Equal(t, "my-namespace", v.Namespace, "namespace should be preserved so the warning can fire") -} +// func TestValkeyFromCRD(t *testing.T) { +// for name, tc := range map[string]struct { +// obj *pgratorv1.Valkey +// wantErr bool +// }{ +// "single node no policy": { +// obj: valkeyObj("foo", pgratorv1.ValkeyTierSingleNode, pgratorv1.ValkeyMemory4GB, ""), +// }, +// "high availability with policy": { +// obj: valkeyObj("bar", pgratorv1.ValkeyTierHighAvailability, pgratorv1.ValkeyMemory28GB, pgratorv1.ValkeyMaxMemoryPolicyVolatileTTL), +// }, +// "bad tier": { +// obj: valkeyObj("x", "MegaNode", pgratorv1.ValkeyMemory4GB, ""), +// wantErr: true, +// }, +// "bad memory": { +// obj: valkeyObj("x", pgratorv1.ValkeyTierSingleNode, "999GB", ""), +// wantErr: true, +// }, +// "bad policy": { +// obj: valkeyObj("x", pgratorv1.ValkeyTierSingleNode, pgratorv1.ValkeyMemory4GB, "never-evict"), +// wantErr: true, +// }, +// } { +// t.Run(name, func(t *testing.T) { +// got, err := valkeyFromCRD(tc.obj) +// if tc.wantErr { +// require.Error(t, err) +// } else { +// require.NoError(t, err) +// require.NotNil(t, got) +// } +// }) +// } +// } -// --------------------------------------------------------------------------- -// valkeyFromCRD -// --------------------------------------------------------------------------- +// func TestValkeyFromCRD_AllMemorySizes(t *testing.T) { +// memories := []pgratorv1.ValkeyMemory{ +// pgratorv1.ValkeyMemory1GB, +// pgratorv1.ValkeyMemory4GB, +// pgratorv1.ValkeyMemory8GB, +// pgratorv1.ValkeyMemory14GB, +// pgratorv1.ValkeyMemory28GB, +// pgratorv1.ValkeyMemory56GB, +// pgratorv1.ValkeyMemory112GB, +// pgratorv1.ValkeyMemory200GB, +// } +// for _, mem := range memories { +// t.Run(string(mem), func(t *testing.T) { +// got, err := valkeyFromCRD(valkeyObj("v", pgratorv1.ValkeyTierSingleNode, mem, "")) +// require.NoError(t, err) +// require.NotNil(t, got) +// }) +// } +// } -func TestValkeyFromCRD(t *testing.T) { - for name, tc := range map[string]struct { - obj *pgratorv1.Valkey - wantErr bool - }{ - "single node no policy": { - obj: valkeyObj("foo", pgratorv1.ValkeyTierSingleNode, pgratorv1.ValkeyMemory4GB, ""), - }, - "high availability with policy": { - obj: valkeyObj("bar", pgratorv1.ValkeyTierHighAvailability, pgratorv1.ValkeyMemory28GB, pgratorv1.ValkeyMaxMemoryPolicyVolatileTTL), - }, - "bad tier": { - obj: valkeyObj("x", "MegaNode", pgratorv1.ValkeyMemory4GB, ""), - wantErr: true, - }, - "bad memory": { - obj: valkeyObj("x", pgratorv1.ValkeyTierSingleNode, "999GB", ""), - wantErr: true, - }, - "bad policy": { - obj: valkeyObj("x", pgratorv1.ValkeyTierSingleNode, pgratorv1.ValkeyMemory4GB, "never-evict"), - wantErr: true, - }, - } { - t.Run(name, func(t *testing.T) { - got, err := valkeyFromCRD(tc.obj) - if tc.wantErr { - require.Error(t, err) - } else { - require.NoError(t, err) - require.NotNil(t, got) - } - }) - } -} +// func TestValkeyFromCRD_AllMaxMemoryPolicies(t *testing.T) { +// policies := []pgratorv1.ValkeyMaxMemoryPolicy{ +// pgratorv1.ValkeyMaxMemoryPolicyAllkeysLFU, +// pgratorv1.ValkeyMaxMemoryPolicyAllkeysLRU, +// pgratorv1.ValkeyMaxMemoryPolicyAllkeysRandom, +// pgratorv1.ValkeyMaxMemoryPolicyNoEviction, +// pgratorv1.ValkeyMaxMemoryPolicyVolatileLFU, +// pgratorv1.ValkeyMaxMemoryPolicyVolatileLRU, +// pgratorv1.ValkeyMaxMemoryPolicyVolatileRandom, +// pgratorv1.ValkeyMaxMemoryPolicyVolatileTTL, +// } +// for _, p := range policies { +// t.Run(string(p), func(t *testing.T) { +// got, err := valkeyFromCRD(valkeyObj("v", pgratorv1.ValkeyTierSingleNode, pgratorv1.ValkeyMemory4GB, p)) +// require.NoError(t, err) +// require.NotNil(t, got) +// }) +// } +// } -func TestValkeyFromCRD_AllMemorySizes(t *testing.T) { - memories := []pgratorv1.ValkeyMemory{ - pgratorv1.ValkeyMemory1GB, - pgratorv1.ValkeyMemory4GB, - pgratorv1.ValkeyMemory8GB, - pgratorv1.ValkeyMemory14GB, - pgratorv1.ValkeyMemory28GB, - pgratorv1.ValkeyMemory56GB, - pgratorv1.ValkeyMemory112GB, - pgratorv1.ValkeyMemory200GB, - } - for _, mem := range memories { - t.Run(string(mem), func(t *testing.T) { - got, err := valkeyFromCRD(valkeyObj("v", pgratorv1.ValkeyTierSingleNode, mem, "")) - require.NoError(t, err) - require.NotNil(t, got) - }) - } -} +// // --------------------------------------------------------------------------- +// // openSearchFromCRD +// // --------------------------------------------------------------------------- -func TestValkeyFromCRD_AllMaxMemoryPolicies(t *testing.T) { - policies := []pgratorv1.ValkeyMaxMemoryPolicy{ - pgratorv1.ValkeyMaxMemoryPolicyAllkeysLFU, - pgratorv1.ValkeyMaxMemoryPolicyAllkeysLRU, - pgratorv1.ValkeyMaxMemoryPolicyAllkeysRandom, - pgratorv1.ValkeyMaxMemoryPolicyNoEviction, - pgratorv1.ValkeyMaxMemoryPolicyVolatileLFU, - pgratorv1.ValkeyMaxMemoryPolicyVolatileLRU, - pgratorv1.ValkeyMaxMemoryPolicyVolatileRandom, - pgratorv1.ValkeyMaxMemoryPolicyVolatileTTL, - } - for _, p := range policies { - t.Run(string(p), func(t *testing.T) { - got, err := valkeyFromCRD(valkeyObj("v", pgratorv1.ValkeyTierSingleNode, pgratorv1.ValkeyMemory4GB, p)) - require.NoError(t, err) - require.NotNil(t, got) - }) - } -} +// func TestOpenSearchFromCRD(t *testing.T) { +// for name, tc := range map[string]struct { +// obj *pgratorv1.OpenSearch +// wantErr bool +// }{ +// "single node v1": { +// obj: openSearchObj("idx", pgratorv1.OpenSearchTierSingleNode, pgratorv1.OpenSearchMemory2GB, pgratorv1.OpenSearchVersionV1, 10), +// }, +// "high availability v2.19": { +// obj: openSearchObj("idx", pgratorv1.OpenSearchTierHighAvailability, pgratorv1.OpenSearchMemory32GB, pgratorv1.OpenSearchVersionV2_19, 200), +// }, +// "bad tier": { +// obj: openSearchObj("idx", "SuperNode", pgratorv1.OpenSearchMemory4GB, pgratorv1.OpenSearchVersionV2, 10), +// wantErr: true, +// }, +// "bad memory": { +// obj: openSearchObj("idx", pgratorv1.OpenSearchTierSingleNode, "3GB", pgratorv1.OpenSearchVersionV2, 10), +// wantErr: true, +// }, +// "bad version": { +// obj: openSearchObj("idx", pgratorv1.OpenSearchTierSingleNode, pgratorv1.OpenSearchMemory4GB, "99", 10), +// wantErr: true, +// }, +// } { +// t.Run(name, func(t *testing.T) { +// got, err := openSearchFromCRD(tc.obj) +// if tc.wantErr { +// require.Error(t, err) +// } else { +// require.NoError(t, err) +// require.NotNil(t, got) +// } +// }) +// } +// } -// --------------------------------------------------------------------------- -// openSearchFromCRD -// --------------------------------------------------------------------------- +// func TestOpenSearchFromCRD_AllMemorySizes(t *testing.T) { +// memories := []pgratorv1.OpenSearchMemory{ +// pgratorv1.OpenSearchMemory2GB, +// pgratorv1.OpenSearchMemory4GB, +// pgratorv1.OpenSearchMemory8GB, +// pgratorv1.OpenSearchMemory16GB, +// pgratorv1.OpenSearchMemory32GB, +// pgratorv1.OpenSearchMemory64GB, +// } +// for _, mem := range memories { +// t.Run(string(mem), func(t *testing.T) { +// got, err := openSearchFromCRD(openSearchObj("idx", pgratorv1.OpenSearchTierSingleNode, mem, pgratorv1.OpenSearchVersionV2, 10)) +// require.NoError(t, err) +// require.NotNil(t, got) +// }) +// } +// } -func TestOpenSearchFromCRD(t *testing.T) { - for name, tc := range map[string]struct { - obj *pgratorv1.OpenSearch - wantErr bool - }{ - "single node v1": { - obj: openSearchObj("idx", pgratorv1.OpenSearchTierSingleNode, pgratorv1.OpenSearchMemory2GB, pgratorv1.OpenSearchVersionV1, 10), - }, - "high availability v2.19": { - obj: openSearchObj("idx", pgratorv1.OpenSearchTierHighAvailability, pgratorv1.OpenSearchMemory32GB, pgratorv1.OpenSearchVersionV2_19, 200), - }, - "bad tier": { - obj: openSearchObj("idx", "SuperNode", pgratorv1.OpenSearchMemory4GB, pgratorv1.OpenSearchVersionV2, 10), - wantErr: true, - }, - "bad memory": { - obj: openSearchObj("idx", pgratorv1.OpenSearchTierSingleNode, "3GB", pgratorv1.OpenSearchVersionV2, 10), - wantErr: true, - }, - "bad version": { - obj: openSearchObj("idx", pgratorv1.OpenSearchTierSingleNode, pgratorv1.OpenSearchMemory4GB, "99", 10), - wantErr: true, - }, - } { - t.Run(name, func(t *testing.T) { - got, err := openSearchFromCRD(tc.obj) - if tc.wantErr { - require.Error(t, err) - } else { - require.NoError(t, err) - require.NotNil(t, got) - } - }) - } -} +// func TestOpenSearchFromCRD_AllVersions(t *testing.T) { +// versions := []pgratorv1.OpenSearchVersion{ +// pgratorv1.OpenSearchVersionV1, +// pgratorv1.OpenSearchVersionV2, +// pgratorv1.OpenSearchVersionV2_19, +// pgratorv1.OpenSearchVersionV3_3, +// } +// for _, v := range versions { +// t.Run(string(v), func(t *testing.T) { +// got, err := openSearchFromCRD(openSearchObj("idx", pgratorv1.OpenSearchTierSingleNode, pgratorv1.OpenSearchMemory4GB, v, 10)) +// require.NoError(t, err) +// require.NotNil(t, got) +// }) +// } +// } -func TestOpenSearchFromCRD_AllMemorySizes(t *testing.T) { - memories := []pgratorv1.OpenSearchMemory{ - pgratorv1.OpenSearchMemory2GB, - pgratorv1.OpenSearchMemory4GB, - pgratorv1.OpenSearchMemory8GB, - pgratorv1.OpenSearchMemory16GB, - pgratorv1.OpenSearchMemory32GB, - pgratorv1.OpenSearchMemory64GB, - } - for _, mem := range memories { - t.Run(string(mem), func(t *testing.T) { - got, err := openSearchFromCRD(openSearchObj("idx", pgratorv1.OpenSearchTierSingleNode, mem, pgratorv1.OpenSearchVersionV2, 10)) - require.NoError(t, err) - require.NotNil(t, got) - }) - } -} +// // --------------------------------------------------------------------------- +// // helpers +// // --------------------------------------------------------------------------- -func TestOpenSearchFromCRD_AllVersions(t *testing.T) { - versions := []pgratorv1.OpenSearchVersion{ - pgratorv1.OpenSearchVersionV1, - pgratorv1.OpenSearchVersionV2, - pgratorv1.OpenSearchVersionV2_19, - pgratorv1.OpenSearchVersionV3_3, - } - for _, v := range versions { - t.Run(string(v), func(t *testing.T) { - got, err := openSearchFromCRD(openSearchObj("idx", pgratorv1.OpenSearchTierSingleNode, pgratorv1.OpenSearchMemory4GB, v, 10)) - require.NoError(t, err) - require.NotNil(t, got) - }) - } -} +// func valkeyObj(name string, tier pgratorv1.ValkeyTier, mem pgratorv1.ValkeyMemory, policy pgratorv1.ValkeyMaxMemoryPolicy) *pgratorv1.Valkey { +// v := &pgratorv1.Valkey{} +// v.Name = name +// v.Spec.Tier = tier +// v.Spec.Memory = mem +// v.Spec.MaxMemoryPolicy = policy +// return v +// } -// --------------------------------------------------------------------------- -// helpers -// --------------------------------------------------------------------------- +// func openSearchObj(name string, tier pgratorv1.OpenSearchTier, mem pgratorv1.OpenSearchMemory, version pgratorv1.OpenSearchVersion, storageGB int) *pgratorv1.OpenSearch { +// o := &pgratorv1.OpenSearch{} +// o.Name = name +// o.Spec.Tier = tier +// o.Spec.Memory = mem +// o.Spec.Version = version +// o.Spec.StorageGB = storageGB +// return o +// } -func valkeyObj(name string, tier pgratorv1.ValkeyTier, mem pgratorv1.ValkeyMemory, policy pgratorv1.ValkeyMaxMemoryPolicy) *pgratorv1.Valkey { - v := &pgratorv1.Valkey{} - v.Name = name - v.Spec.Tier = tier - v.Spec.Memory = mem - v.Spec.MaxMemoryPolicy = policy - return v -} - -func openSearchObj(name string, tier pgratorv1.OpenSearchTier, mem pgratorv1.OpenSearchMemory, version pgratorv1.OpenSearchVersion, storageGB int) *pgratorv1.OpenSearch { - o := &pgratorv1.OpenSearch{} - o.Name = name - o.Spec.Tier = tier - o.Spec.Memory = mem - o.Spec.Version = version - o.Spec.StorageGB = storageGB - return o -} - -// Ensure the package-level scheme has pgratorv1 types registered (compile-time check). -var ( - _ runtime.Object = (*pgratorv1.Valkey)(nil) - _ runtime.Object = (*pgratorv1.OpenSearch)(nil) -) +// // Ensure the package-level scheme has pgratorv1 types registered (compile-time check). +// var ( +// _ runtime.Object = (*pgratorv1.Valkey)(nil) +// _ runtime.Object = (*pgratorv1.OpenSearch)(nil) +// ) diff --git a/internal/naisapi/apply.go b/internal/naisapi/apply.go new file mode 100644 index 00000000..9cc0714c --- /dev/null +++ b/internal/naisapi/apply.go @@ -0,0 +1,55 @@ +package naisapi + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "strings" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +func ApplyManifests(ctx context.Context, teamSlug, environmentName string, manifests []unstructured.Unstructured) error { + const url = "%v/api/v1/teams/%v/environments/%v/apply" + + user, err := GetAuthenticatedUser(ctx) + if err != nil { + return fmt.Errorf("failed to get authenticated user: %w", err) + } + + bodyData := struct { + Resources []unstructured.Unstructured `json:"resources"` + }{ + Resources: manifests, + } + + body := &bytes.Buffer{} + if err := json.NewEncoder(body).Encode(bodyData); err != nil { + return fmt.Errorf("failed to encode manifests to YAML: %w", err) + } + + uri := fmt.Sprintf(url, strings.TrimSuffix(user.APIURL(), "/graphql"), teamSlug, environmentName) + fmt.Println(uri) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri, body) + if err != nil { + return fmt.Errorf("failed to create HTTP request: %w", err) + } + + resp, err := user.HTTPClient(ctx).Do(req) + if err != nil { + return fmt.Errorf("failed to send HTTP request: %w", err) + } + defer resp.Body.Close() + + for k, v := range req.Header { + fmt.Printf("%s: %s\n", k, v) + } + + io.Copy(os.Stdout, resp.Body) + + return nil +} diff --git a/internal/naisapi/auth/localhost.go b/internal/naisapi/auth/localhost.go index 92478d40..7f93a348 100644 --- a/internal/naisapi/auth/localhost.go +++ b/internal/naisapi/auth/localhost.go @@ -36,10 +36,13 @@ func (l *LocalhostUser) APIURL() string { return fmt.Sprintf("http://%s/graphql", l.ConsoleHost()) } -func (l *LocalhostUser) HTTPClient(_ context.Context) *http.Client { - return &http.Client{ - Transport: l.RoundTripper(http.DefaultTransport), +func (l *LocalhostUser) HTTPClient(ctx context.Context) *http.Client { + if os.Getenv("NAIS_API_LOCAL_EMAIL") != "" { + return &http.Client{ + Transport: l.RoundTripper(http.DefaultTransport), + } } + return oauth2.NewClient(ctx, l.ts) } func (l *LocalhostUser) RoundTripper(base http.RoundTripper) http.RoundTripper { diff --git a/mise/tasks/ci/release-info.sh b/mise/tasks/ci/release-info.sh index 586844fa..5711d0be 100755 --- a/mise/tasks/ci/release-info.sh +++ b/mise/tasks/ci/release-info.sh @@ -52,8 +52,13 @@ main() { return fi + if [[ "${PRE_RELEASE:-}" == "true" && -n "${PR_NUMBER:-}" ]]; then + version="${version}-rc.${PR_NUMBER}" + fi + output "changelog" "$changelog" output "version" "$version" + output "pre_release" "${PRE_RELEASE:-false}" } output() {