diff --git a/.github/workflows/containerize.yaml b/.github/workflows/containerize.yaml new file mode 100644 index 0000000..0325f78 --- /dev/null +++ b/.github/workflows/containerize.yaml @@ -0,0 +1,48 @@ +# +name: Create and publish a Docker image + +# Configures this workflow to run every time a change is pushed to the branch called `release`. +on: + pull_request: + branches: ["main"] + +# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds. +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu. +jobs: + build-and-push-image: + runs-on: ubuntu-latest + # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job. + permissions: + contents: read + packages: write + # + steps: + - name: Checkout repository + uses: actions/checkout@v4 + # Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here. + - name: Log in to the Container registry + uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels. + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages. + # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository. + # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. + - name: Build and push Docker image + uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3081f2d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,48 @@ +FROM golang:1.21 AS build-stage + +WORKDIR /app + +COPY go.mod go.sum . +RUN go mod download + +COPY . . + +RUN apt-get update && \ + apt-get upgrade -y + +RUN apt-get install -y build-essential make + +RUN make build + +FROM debian:12-slim AS build-release-stage + +RUN apt-get update && \ + apt-get upgrade -y + +RUN apt-get install -y \ + git \ + docker \ + docker-compose + +# Create a system group named "user" with the -r flag +RUN groupadd -g 1000 -r user + +# Create a system user named "user" and add it to the "user" group with the -r and -g flags +RUN useradd -r -u 1000 -g 1000 user + +RUN usermod -aG docker user +Run newgrp docker + +WORKDIR /workdir + +# Change the ownership of the working directory to the non-root user "user" + +RUN chown -R user:user /workdir + +# Switch to the non-root user "user" +USER user + + +COPY --from=build-stage /app/upstream-watch /app/upstream-watch + +CMD ["/app/upstream-watch", "/workdir"] \ No newline at end of file diff --git a/Makefile b/Makefile index 5c1da2b..38973b8 100644 --- a/Makefile +++ b/Makefile @@ -22,3 +22,5 @@ testcoverage: $(GOTEST) -coverprofile coverage.out ./... && go tool cover -html=coverage.out && rm coverage.out lint: staticcheck -f stylish github.com/andresterba/upstream-watch/... +containerize: + docker build -t upstream-watch:test . diff --git a/README.md b/README.md index 0739c74..5a3bb1e 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ git repository. There are two different modes: - single directory -- Subdiretorie per service +- Subdirectories per service ## Single directory diff --git a/cmd/main.go b/cmd/main.go index 9aee77f..3a64729 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,8 +1,11 @@ package main import ( + "fmt" "log" + "os" "os/exec" + "path" "time" "github.com/andresterba/upstream-watch/internal/config" @@ -10,8 +13,9 @@ import ( "github.com/andresterba/upstream-watch/internal/updater" ) -func pullUpstreamRepository() { +func pullUpstreamRepository(runPath string) { runCommand := exec.Command("git", "pull") + runCommand.Dir = runPath output, err := runCommand.CombinedOutput() if err != nil { log.Fatalf("Failed to pull upstream repository\n%s\n", output) @@ -20,11 +24,11 @@ func pullUpstreamRepository() { log.Print("Successfully pulled upstream repository") } -func updateSubdirectories(loadedConfig *config.Config, db updater.Database) { - pullUpstreamRepository() +func updateSubdirectories(runPath string, loadedConfig *config.Config, db updater.Database) { + pullUpstreamRepository(runPath) ds := files.NewDirectoryScanner(loadedConfig.IgnoreFolders) - directories, err := ds.ListDirectories() + directories, err := ds.ListDirectories(runPath) if err != nil { log.Fatalf("failed to list directories\n%s\n", err) } @@ -55,10 +59,10 @@ func updateSubdirectories(loadedConfig *config.Config, db updater.Database) { <-time.After(loadedConfig.RetryInterval * time.Second) } -func updateRootRepository(loadedConfig *config.Config, db updater.Database) { - pullUpstreamRepository() +func updateRootRepository(runPath string, loadedConfig *config.Config, db updater.Database) { + pullUpstreamRepository(runPath) - subdirectory := "." + subdirectory := path.Join(runPath, "/") updateConfig, err := config.GetUpdateConfig(subdirectory + "/.update-hooks.yaml") if err != nil { log.Printf("Failed to update root: %+v", err) @@ -81,23 +85,35 @@ func updateRootRepository(loadedConfig *config.Config, db updater.Database) { <-time.After(loadedConfig.RetryInterval * time.Second) } +const configName = ".upstream-watch.yaml" + func main() { + args := os.Args + if len(args) != 2 { + fmt.Printf("Please provide specify the directory to run in as arg.\n") + os.Exit(0) + } + + runPath := os.Args[1] + + pathToConfig := path.Join(runPath, configName) + for { - loadedConfig, err := config.GetConfig(".upstream-watch.yaml") + loadedConfig, err := config.GetConfig(pathToConfig) if err != nil { log.Fatal(err) } - updateDb := updater.NewDatabase() + updateDb := updater.NewDatabase(runPath) rootDirectoryeMode := loadedConfig.SingleDirectoryMode switch rootDirectoryeMode { case true: - updateRootRepository(loadedConfig, updateDb) + updateRootRepository(runPath, loadedConfig, updateDb) case false: - updateSubdirectories(loadedConfig, updateDb) + updateSubdirectories(runPath, loadedConfig, updateDb) } } } diff --git a/internal/files/files.go b/internal/files/files.go index 80d31c7..57286d6 100644 --- a/internal/files/files.go +++ b/internal/files/files.go @@ -23,9 +23,9 @@ func NewDirectoryScanner(directoriesToIgnore []string) *DirectoryScanner { } } -func (ds *DirectoryScanner) ListDirectories() ([]string, error) { +func (ds *DirectoryScanner) ListDirectories(runPath string) ([]string, error) { subdirectories := []string{} - entries, err := os.ReadDir(".") + entries, err := os.ReadDir(runPath) if err != nil { return nil, fmt.Errorf("failed to list directories %v", err) } diff --git a/internal/files/files_test.go b/internal/files/files_test.go index ed3e04b..ddbfc6f 100644 --- a/internal/files/files_test.go +++ b/internal/files/files_test.go @@ -74,7 +74,7 @@ func TestDirectoryScanner_ListDirectories(t *testing.T) { ds := &DirectoryScanner{ directoriesToIgnore: tt.fields.directoriesToIgnore, } - got, err := ds.ListDirectories() + got, err := ds.ListDirectories(".") if (err != nil) != tt.wantErr { t.Errorf("DirectoryScanner.ListDirectories() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/internal/updater/updateDB.go b/internal/updater/updateDB.go index 28ddd5f..dbfae44 100644 --- a/internal/updater/updateDB.go +++ b/internal/updater/updateDB.go @@ -3,12 +3,15 @@ package updater import ( "fmt" "log" + "path" "sync" "github.com/jmoiron/sqlx" _ "github.com/mattn/go-sqlite3" ) +const DATABASE_FILE_NAME = ".upstream-watch.sqlite" + type Database interface { AddEntry(Entry) error GetEntry(Entry) (Entry, error) @@ -31,11 +34,11 @@ const schema = `CREATE TABLE modules ( updated boolean, PRIMARY KEY (name, git_commit));` -func NewDatabase() Database { +func NewDatabase(runDir string) Database { // this Pings the database trying to connect // use sqlx.Open() for sql.Open() semantics - db, err := sqlx.Connect("sqlite3", "./.upstream-watch.sqlite") + db, err := sqlx.Connect("sqlite3", path.Join(runDir, DATABASE_FILE_NAME)) if err != nil { log.Fatalln(err) }