Skip to content

Commit

Permalink
Refactored into multiple packages
Browse files Browse the repository at this point in the history
  • Loading branch information
applejag committed Apr 5, 2024
1 parent 1567a35 commit f82622a
Show file tree
Hide file tree
Showing 20 changed files with 719 additions and 516 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
// SPDX-License-Identifier: CC0-1.0
{
"cSpell.words": [
"filestore",
"fstore",
"gonic",
"jelease",
"newreleases",
"Templ",
"templating",
"tmpl",
"yamlpath",
"zerolog"
],
"files.exclude": {
Expand Down
4 changes: 2 additions & 2 deletions cmd/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ package cmd
import (
"fmt"

"github.com/RiskIdent/jelease/pkg/patch"
"github.com/RiskIdent/jelease/pkg/config"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
)
Expand All @@ -42,7 +42,7 @@ var applyCmd = &cobra.Command{
}
log.Info().Str("package", pkgName).Msg("Found package config")

tmplCtx := patch.TemplateContext{
tmplCtx := config.TemplateContext{
Package: pkgName,
Version: version,
JiraIssue: applyFlags.jiraIssueKey,
Expand Down
9 changes: 9 additions & 0 deletions pkg/config/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,17 @@ import (
"github.com/spf13/pflag"
)

// Template is a parsed Go [text/template] string, that has additional
// encoders implemented so it can be used in config files and CLI flags.
type Template template.Template

// TemplateContext is the common data passed into templates when executing them.
type TemplateContext struct {
Package string
Version string
JiraIssue string
}

// Ensure the type implements the interfaces
var _ pflag.Value = &Template{}
var _ encoding.TextUnmarshaler = &Template{}
Expand Down
65 changes: 65 additions & 0 deletions pkg/patch/apply.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// SPDX-FileCopyrightText: 2022 Risk.Ident GmbH <[email protected]>
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.

package patch

import (
"errors"
"fmt"

"github.com/RiskIdent/jelease/pkg/config"
"github.com/RiskIdent/jelease/pkg/patch/filestore"
"github.com/RiskIdent/jelease/pkg/patch/patches"
)

// ApplyMany applies a series of patches in sequence using [Apply].
func ApplyMany(repoDir string, patches []config.PackageRepoPatch, tmplCtx config.TemplateContext) error {
for _, p := range patches {
if err := Apply(repoDir, p, tmplCtx); err != nil {
return err
}
}
return nil
}

// Apply applies a single patch to the repository.
func Apply(repoDir string, patch config.PackageRepoPatch, tmplCtx config.TemplateContext) error {
fstore := filestore.NewCached(repoDir)
defer fstore.Close()
switch {
case patch.Regex != nil:
if err := patches.ApplyRegexPatch(fstore, tmplCtx, *patch.Regex); err != nil {
return fmt.Errorf("regex patch: %w", err)
}
case patch.YAML != nil:
if err := patches.ApplyYAMLPatch(fstore, tmplCtx, *patch.YAML); err != nil {
return fmt.Errorf("yaml patch: %w", err)
}
case patch.HelmDepUpdate != nil:
// Flush the store as we need the up-to-date changes on disk
if err := fstore.Flush(); err != nil {
return err
}
if err := patches.ApplyHelmDepUpdatePatch(repoDir, tmplCtx, *patch.HelmDepUpdate); err != nil {
return fmt.Errorf("exec patch: %w", err)
}
default:
return errors.New("missing patch type config")
}

return fstore.Close()
}
66 changes: 11 additions & 55 deletions pkg/patch/filestore.go → pkg/patch/filestore/cached.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,51 +15,39 @@
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.

package patch
package filestore

import (
"io/fs"
"os"
"path/filepath"
)

// FileStore is a minimal abstraction over reading and writing files
// that is used in the patching steps (e.g yaml patch, regex patch, etc).
//
// This abstraction allows us to mock the filesystem during testing,
// as well as adding additional features like caching when you have
// multiple patches on the same file (see [CachedFileStore]).
type FileStore interface {
ReadFile(path string) ([]byte, error)
WriteFile(path string, content []byte) error
Close() error
}

func NewCachedFileStore(dir string) *CachedFileStore {
return &CachedFileStore{
func NewCached(dir string) *Cached {
return &Cached{
Dir: dir,
files: map[string]*File{},
}
}

// CachedFileStore is a [FileStore] that uses the OS' file system,
// Cached is a [FileStore] that uses the OS' file system,
// but adds in-memory caching in between.
// The files are never written to disk until the [CachedFileStore.Flush]
// The files are never written to disk until the [Cached.Flush]
// or [FileStore.Close] method is called.
type CachedFileStore struct {
type Cached struct {
Dir string
files map[string]*File
}

// ensure it implements the interface
var _ FileStore = &CachedFileStore{}
var _ FileStore = &Cached{}

type File struct {
Content []byte
Mode fs.FileMode
}

func (s *CachedFileStore) ReadFile(path string) ([]byte, error) {
func (s *Cached) ReadFile(path string) ([]byte, error) {
path = filepath.Clean(path)
if file, ok := s.files[path]; ok {
return file.Content, nil
Expand All @@ -79,7 +67,7 @@ func (s *CachedFileStore) ReadFile(path string) ([]byte, error) {
return content, nil
}

func (s *CachedFileStore) WriteFile(path string, content []byte) error {
func (s *Cached) WriteFile(path string, content []byte) error {
path = filepath.Clean(path)
if _, ok := s.files[path]; !ok {
// Load it
Expand All @@ -92,7 +80,7 @@ func (s *CachedFileStore) WriteFile(path string, content []byte) error {
return nil
}

func (s *CachedFileStore) Flush() error {
func (s *Cached) Flush() error {
for path, file := range s.files {
if err := os.WriteFile(filepath.Join(s.Dir, path), file.Content, file.Mode); err != nil {
return err
Expand All @@ -102,38 +90,6 @@ func (s *CachedFileStore) Flush() error {
return nil
}

func (s *CachedFileStore) Close() error {
func (s *Cached) Close() error {
return s.Flush()
}

func NewTestFileStore(files map[string]string) *TestFileStore {
return &TestFileStore{
files: files,
}
}

// TestFileStore is a [FileStore] used during Go unit tests.
// It never touches the OS' underlying file system.
type TestFileStore struct {
files map[string]string
}

// ensure it implements the interface
var _ FileStore = &TestFileStore{}

func (s *TestFileStore) ReadFile(path string) ([]byte, error) {
content, ok := s.files[path]
if !ok {
return nil, os.ErrNotExist
}
return []byte(content), nil
}

func (s *TestFileStore) WriteFile(path string, content []byte) error {
s.files[path] = string(content)
return nil
}

func (s *TestFileStore) Close() error {
return nil
}
32 changes: 32 additions & 0 deletions pkg/patch/filestore/filestore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: 2024 Risk.Ident GmbH <[email protected]>
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.

// Package filestore contains a simple and minimal abstraction over
// OS operations for reading and writing to files.
package filestore

// FileStore is a minimal abstraction over reading and writing files
// that is used in the patching steps (e.g yaml patch, regex patch, etc).
//
// This abstraction allows us to mock the filesystem during testing,
// as well as adding additional features like caching when you have
// multiple patches on the same file (see [Cached]).
type FileStore interface {
ReadFile(path string) ([]byte, error)
WriteFile(path string, content []byte) error
Close() error
}
52 changes: 52 additions & 0 deletions pkg/patch/filestore/test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: 2024 Risk.Ident GmbH <[email protected]>
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.

package filestore

import "os"

func NewTestFileStore(files map[string]string) *TestFileStore {
return &TestFileStore{
files: files,
}
}

// TestFileStore is a [FileStore] used during Go unit tests.
// It never touches the OS' underlying file system.
type TestFileStore struct {
files map[string]string
}

// ensure it implements the interface
var _ FileStore = &TestFileStore{}

func (s *TestFileStore) ReadFile(path string) ([]byte, error) {
content, ok := s.files[path]
if !ok {
return nil, os.ErrNotExist
}
return []byte(content), nil
}

func (s *TestFileStore) WriteFile(path string, content []byte) error {
s.files[path] = string(content)
return nil
}

func (s *TestFileStore) Close() error {
return nil
}
Loading

0 comments on commit f82622a

Please sign in to comment.