diff --git a/.dockerignore b/.dockerignore
index 9accc734..a52e7ea2 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -4,7 +4,7 @@
docs/
README.md
.env
-falcon9.db
+sqlite3.db
# ui
ui/node_modules
diff --git a/BUILDING b/BUILDING
new file mode 100644
index 00000000..b5424dfd
--- /dev/null
+++ b/BUILDING
@@ -0,0 +1,4 @@
+1. Clone the repository
+2. Build the Docker image:
+
+ docker build -t gitploy .
diff --git a/BUILDING_OSS b/BUILDING_OSS
new file mode 100644
index 00000000..d32a12f0
--- /dev/null
+++ b/BUILDING_OSS
@@ -0,0 +1,4 @@
+1. Clone the repository
+2. Build the Docker image:
+
+ docker build -t gitploy --build-arg "OSS=true" .
diff --git a/Dockerfile b/Dockerfile
index cf9054da..effadf89 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,6 @@
# Build the server binary file.
FROM golang:1.15 AS server
+ARG OSS=false
WORKDIR /server
@@ -7,10 +8,17 @@ COPY go.mod go.sum ./
RUN go mod download
COPY . .
-RUN go install ./cmd/server
+RUN if [ "${OSS}" = "false" ]; then \
+ echo "Build the enterprise edition"; \
+ go build -o gitploy-server ./cmd/server; \
+ else \
+ echo "Build the community edition"; \
+ go build -o gitploy-server -tags "oss" ./cmd/server; \
+ fi
# Build UI.
FROM node:14.17.0 AS ui
+ARG OSS=false
WORKDIR /ui
@@ -20,6 +28,7 @@ COPY ./ui/package.json ./ui/package-lock.json ./
RUN npm install --silent
COPY ./ui ./
+ENV REACT_APP_GITPLOY_OSS="${OSS}"
RUN npm run build
# Copy to the final image.
@@ -30,10 +39,10 @@ WORKDIR /app
# Create DB
RUN mkdir /data
-COPY --from=server --chown=root:root /server/LICENSE /server/NOTICE .
-COPY --from=server --chown=root:root /go/bin/server /go/bin/server
+COPY --from=server --chown=root:root /server/LICENSE /server/NOTICE ./
+COPY --from=server --chown=root:root /server/gitploy-server /go/bin/gitploy-server
# Copy UI output into the assets directory.
COPY --from=ui --chown=root:root /ui/build/ /app/
-ENTRYPOINT [ "/go/bin/server" ]
\ No newline at end of file
+ENTRYPOINT [ "/go/bin/gitploy-server" ]
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index c0cf1592..413d0d5a 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,5 +1,14 @@
Copyright 2021 Gitploy.IO, Inc.
+The Gitploy Community Edition is licensed under the Apache License,
+Version 2.0 (the "Apache License").
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+The source files in this repository are under the Apache License
+basically, but some files are under the Gitploy Non-Commercial License.
+The header of files indicating which license they are under.
+
The Gitploy Enterprise Edition is licensed under the Gitploy
Non-Commercial License (the "Non-Commercial License"). A copy of
the Non-Commercial License is provided below.
@@ -20,8 +29,7 @@ software that would otherwise infringe either the contributor's
copyright in it.
1. You must limit use of this software in any manner primarily
- intended for commercial advantage or private monetary compensation
- to the count of user limit.
+ intended for commercial advantage or private monetary compensation.
This limit does not apply to use in developing feedback or extensions
that you contribute back to those giving this license.
diff --git a/cmd/server/db.go b/cmd/server/db.go
index 14c6c622..5af8d6fc 100644
--- a/cmd/server/db.go
+++ b/cmd/server/db.go
@@ -1,3 +1,9 @@
+// Copyright 2021 Gitploy.IO Inc. All rights reserved.
+// Use of this source code is governed by the Gitploy Non-Commercial License
+// that can be found in the LICENSE file.
+
+// +build !oss
+
package main
import (
diff --git a/cmd/server/db_oss.go b/cmd/server/db_oss.go
new file mode 100644
index 00000000..62af9583
--- /dev/null
+++ b/cmd/server/db_oss.go
@@ -0,0 +1,18 @@
+// +build oss
+
+package main
+
+import (
+ "fmt"
+
+ "entgo.io/ent/dialect"
+ "github.com/gitploy-io/gitploy/ent"
+)
+
+func OpenDB(driver string, dsn string) (*ent.Client, error) {
+ if driver != dialect.SQLite {
+ return nil, fmt.Errorf("The community edition support sqlite only.")
+ }
+
+ return ent.Open(driver, dsn)
+}
diff --git a/internal/server/api/v1/repos/approval.go b/internal/server/api/v1/repos/approval.go
index ba6b27ae..1c268acf 100644
--- a/internal/server/api/v1/repos/approval.go
+++ b/internal/server/api/v1/repos/approval.go
@@ -1,3 +1,9 @@
+// Copyright 2021 Gitploy.IO Inc. All rights reserved.
+// Use of this source code is governed by the Gitploy Non-Commercial License
+// that can be found in the LICENSE file.
+
+// +build !oss
+
package repos
import (
diff --git a/internal/server/api/v1/repos/approval_oss.go b/internal/server/api/v1/repos/approval_oss.go
new file mode 100644
index 00000000..f91c4482
--- /dev/null
+++ b/internal/server/api/v1/repos/approval_oss.go
@@ -0,0 +1,36 @@
+// +build oss
+
+package repos
+
+import (
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+
+ "github.com/gitploy-io/gitploy/ent"
+ gb "github.com/gitploy-io/gitploy/internal/server/global"
+)
+
+func (r *Repo) ListApprovals(c *gin.Context) {
+ gb.Response(c, http.StatusOK, make([]*ent.Approval, 0))
+}
+
+func (r *Repo) GetApproval(c *gin.Context) {
+ gb.Response(c, http.StatusNotFound, nil)
+}
+
+func (r *Repo) GetMyApproval(c *gin.Context) {
+ gb.Response(c, http.StatusNotFound, nil)
+}
+
+func (r *Repo) CreateApproval(c *gin.Context) {
+ gb.ErrorResponse(c, http.StatusPaymentRequired, "It is limited to the community edition.")
+}
+
+func (r *Repo) UpdateMyApproval(c *gin.Context) {
+ gb.ErrorResponse(c, http.StatusPaymentRequired, "It is limited to the community edition.")
+}
+
+func (r *Repo) DeleteApproval(c *gin.Context) {
+ gb.ErrorResponse(c, http.StatusPaymentRequired, "It is limited to the community edition.")
+}
diff --git a/internal/server/api/v1/repos/lock.go b/internal/server/api/v1/repos/lock.go
index c039f748..7eac7a26 100644
--- a/internal/server/api/v1/repos/lock.go
+++ b/internal/server/api/v1/repos/lock.go
@@ -1,3 +1,9 @@
+// Copyright 2021 Gitploy.IO Inc. All rights reserved.
+// Use of this source code is governed by the Gitploy Non-Commercial License
+// that can be found in the LICENSE file.
+
+// +build !oss
+
package repos
import (
diff --git a/internal/server/api/v1/repos/lock_oss.go b/internal/server/api/v1/repos/lock_oss.go
new file mode 100644
index 00000000..e604aca3
--- /dev/null
+++ b/internal/server/api/v1/repos/lock_oss.go
@@ -0,0 +1,28 @@
+// +build oss
+
+package repos
+
+import (
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+
+ "github.com/gitploy-io/gitploy/ent"
+ gb "github.com/gitploy-io/gitploy/internal/server/global"
+)
+
+func (r *Repo) ListLocks(c *gin.Context) {
+ gb.Response(c, http.StatusOK, make([]*ent.Lock, 0))
+}
+
+func (r *Repo) CreateLock(c *gin.Context) {
+ gb.ErrorResponse(c, http.StatusPaymentRequired, "It is limited to the community edition.")
+}
+
+func (r *Repo) UpdateLock(c *gin.Context) {
+ gb.ErrorResponse(c, http.StatusPaymentRequired, "It is limited to the community edition.")
+}
+
+func (r *Repo) DeleteLock(c *gin.Context) {
+ gb.ErrorResponse(c, http.StatusPaymentRequired, "It is limited to the community edition.")
+}
diff --git a/internal/server/api/v1/stream/events.go b/internal/server/api/v1/stream/events.go
new file mode 100644
index 00000000..65292172
--- /dev/null
+++ b/internal/server/api/v1/stream/events.go
@@ -0,0 +1,144 @@
+// Copyright 2021 Gitploy.IO Inc. All rights reserved.
+// Use of this source code is governed by the Gitploy Non-Commercial License
+// that can be found in the LICENSE file.
+
+// +build !oss
+
+package stream
+
+import (
+ "context"
+ "fmt"
+ "math/rand"
+ "net/http"
+ "time"
+
+ "github.com/gin-contrib/sse"
+ "github.com/gin-gonic/gin"
+ "go.uber.org/zap"
+
+ "github.com/gitploy-io/gitploy/ent"
+ "github.com/gitploy-io/gitploy/ent/event"
+ gb "github.com/gitploy-io/gitploy/internal/server/global"
+)
+
+// GetEvents streams events of deployment, or approval.
+func (s *Stream) GetEvents(c *gin.Context) {
+ ctx := c.Request.Context()
+
+ v, _ := c.Get(gb.KeyUser)
+ u, _ := v.(*ent.User)
+
+ debugID := randstr()
+
+ events := make(chan *ent.Event, 10)
+
+ // Subscribe events
+ // it'll unsubscribe after the connection is closed.
+ sub := func(e *ent.Event) {
+
+ // Deleted type is always propagated to all.
+ if e.Type == event.TypeDeleted {
+ events <- e
+ return
+ }
+
+ if ok, err := s.hasPermForEvent(ctx, u, e); !ok {
+ s.log.Debug("Skip the event. The user has not the perm.")
+ return
+ } else if err != nil {
+ s.log.Error("It has failed to check the perm.", zap.Error(err))
+ return
+ }
+
+ events <- e
+ }
+ if err := s.i.SubscribeEvent(sub); err != nil {
+ s.log.Error("failed to subscribe notification events", zap.Error(err))
+ gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to connect.")
+ return
+ }
+
+ defer func() {
+ if err := s.i.UnsubscribeEvent(sub); err != nil {
+ s.log.Error("failed to unsubscribe notification events.")
+ }
+
+ close(events)
+ }()
+
+ w := c.Writer
+
+L:
+ for {
+ select {
+ case <-w.CloseNotify():
+ break L
+ case <-time.After(time.Hour):
+ break L
+ case <-time.After(time.Second * 30):
+ c.Render(-1, sse.Event{
+ Event: "ping",
+ Data: "ping",
+ })
+ w.Flush()
+ case e := <-events:
+ c.Render(-1, sse.Event{
+ Event: "event",
+ Data: e,
+ })
+ w.Flush()
+ s.log.Debug("server sent event.", zap.Int("event_id", e.ID), zap.String("debug_id", debugID))
+ }
+ }
+}
+
+// hasPermForEvent checks the user has permission for the event.
+func (s *Stream) hasPermForEvent(ctx context.Context, u *ent.User, e *ent.Event) (bool, error) {
+ if e.Kind == event.KindDeployment {
+ d, err := s.i.FindDeploymentByID(ctx, e.DeploymentID)
+ if err != nil {
+ return false, err
+ }
+
+ if _, err = s.i.FindPermOfRepo(ctx, d.Edges.Repo, u); ent.IsNotFound(err) {
+ return false, nil
+ } else if err != nil {
+ return false, err
+ }
+
+ return true, nil
+ }
+
+ if e.Kind == event.KindApproval {
+ a, err := s.i.FindApprovalByID(ctx, e.ApprovalID)
+ if err != nil {
+ return false, err
+ }
+
+ d, err := s.i.FindDeploymentByID(ctx, a.DeploymentID)
+ if err != nil {
+ return false, err
+ }
+
+ if _, err = s.i.FindPermOfRepo(ctx, d.Edges.Repo, u); ent.IsNotFound(err) {
+ return false, nil
+ } else if err != nil {
+ return false, err
+ }
+
+ return true, nil
+ }
+
+ return false, fmt.Errorf("The type of event is not \"deployment\" or \"approval\".")
+}
+
+func randstr() string {
+ var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+
+ b := make([]rune, 4)
+ for i := range b {
+ b[i] = letterRunes[rand.Intn(len(letterRunes))]
+ }
+ return string(b)
+}
diff --git a/internal/server/api/v1/stream/events_oss.go b/internal/server/api/v1/stream/events_oss.go
new file mode 100644
index 00000000..17b0c531
--- /dev/null
+++ b/internal/server/api/v1/stream/events_oss.go
@@ -0,0 +1,23 @@
+// +build oss
+
+package stream
+
+import (
+ "time"
+
+ "github.com/gin-gonic/gin"
+)
+
+func (s *Stream) GetEvents(c *gin.Context) {
+ w := c.Writer
+
+L:
+ for {
+ select {
+ case <-w.CloseNotify():
+ break L
+ case <-time.After(time.Minute):
+ break L
+ }
+ }
+}
diff --git a/internal/server/api/v1/stream/stream.go b/internal/server/api/v1/stream/stream.go
index 7b44d461..64b9b10d 100644
--- a/internal/server/api/v1/stream/stream.go
+++ b/internal/server/api/v1/stream/stream.go
@@ -1,19 +1,7 @@
package stream
import (
- "context"
- "fmt"
- "math/rand"
- "net/http"
- "time"
-
- "github.com/gin-contrib/sse"
- "github.com/gin-gonic/gin"
"go.uber.org/zap"
-
- "github.com/gitploy-io/gitploy/ent"
- "github.com/gitploy-io/gitploy/ent/event"
- gb "github.com/gitploy-io/gitploy/internal/server/global"
)
type (
@@ -29,124 +17,3 @@ func NewStream(i Interactor) *Stream {
log: zap.L().Named("stream"),
}
}
-
-// GetEvents streams events of deployment, or approval.
-func (s *Stream) GetEvents(c *gin.Context) {
- ctx := c.Request.Context()
-
- v, _ := c.Get(gb.KeyUser)
- u, _ := v.(*ent.User)
-
- debugID := randstr()
-
- events := make(chan *ent.Event, 10)
-
- // Subscribe events
- // it'll unsubscribe after the connection is closed.
- sub := func(e *ent.Event) {
-
- // Deleted type is always propagated to all.
- if e.Type == event.TypeDeleted {
- events <- e
- return
- }
-
- if ok, err := s.hasPermForEvent(ctx, u, e); !ok {
- s.log.Debug("Skip the event. The user has not the perm.")
- return
- } else if err != nil {
- s.log.Error("It has failed to check the perm.", zap.Error(err))
- return
- }
-
- events <- e
- }
- if err := s.i.SubscribeEvent(sub); err != nil {
- s.log.Error("failed to subscribe notification events", zap.Error(err))
- gb.ErrorResponse(c, http.StatusInternalServerError, "It has failed to connect.")
- return
- }
-
- defer func() {
- if err := s.i.UnsubscribeEvent(sub); err != nil {
- s.log.Error("failed to unsubscribe notification events.")
- }
-
- close(events)
- }()
-
- w := c.Writer
-
-L:
- for {
- select {
- case <-w.CloseNotify():
- break L
- case <-time.After(time.Hour):
- break L
- case <-time.After(time.Second * 30):
- c.Render(-1, sse.Event{
- Event: "ping",
- Data: "ping",
- })
- w.Flush()
- case e := <-events:
- c.Render(-1, sse.Event{
- Event: "event",
- Data: e,
- })
- w.Flush()
- s.log.Debug("server sent event.", zap.Int("event_id", e.ID), zap.String("debug_id", debugID))
- }
- }
-}
-
-// hasPermForEvent checks the user has permission for the event.
-func (s *Stream) hasPermForEvent(ctx context.Context, u *ent.User, e *ent.Event) (bool, error) {
- if e.Kind == event.KindDeployment {
- d, err := s.i.FindDeploymentByID(ctx, e.DeploymentID)
- if err != nil {
- return false, err
- }
-
- if _, err = s.i.FindPermOfRepo(ctx, d.Edges.Repo, u); ent.IsNotFound(err) {
- return false, nil
- } else if err != nil {
- return false, err
- }
-
- return true, nil
- }
-
- if e.Kind == event.KindApproval {
- a, err := s.i.FindApprovalByID(ctx, e.ApprovalID)
- if err != nil {
- return false, err
- }
-
- d, err := s.i.FindDeploymentByID(ctx, a.DeploymentID)
- if err != nil {
- return false, err
- }
-
- if _, err = s.i.FindPermOfRepo(ctx, d.Edges.Repo, u); ent.IsNotFound(err) {
- return false, nil
- } else if err != nil {
- return false, err
- }
-
- return true, nil
- }
-
- return false, fmt.Errorf("The type of event is not \"deployment\" or \"approval\".")
-}
-
-func randstr() string {
- var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
-
- b := make([]rune, 4)
- for i := range b {
- b[i] = letterRunes[rand.Intn(len(letterRunes))]
- }
- return string(b)
-}
diff --git a/internal/server/slack/helper.go b/internal/server/slack/helper.go
index 9d3fa177..502ba647 100644
--- a/internal/server/slack/helper.go
+++ b/internal/server/slack/helper.go
@@ -2,8 +2,32 @@ package slack
import (
"strconv"
+
+ "github.com/gitploy-io/gitploy/ent"
+ "github.com/slack-go/slack"
)
+func postResponseMessage(channelID, responseURL, message string) error {
+ _, _, _, err := slack.
+ New("").
+ SendMessage(
+ channelID,
+ slack.MsgOptionResponseURL(responseURL, "ephemeral"),
+ slack.MsgOptionText(message, false),
+ )
+ return err
+}
+
+func postBotMessage(cu *ent.ChatUser, message string) error {
+ _, _, _, err := slack.
+ New(cu.BotToken).
+ SendMessage(
+ cu.ID,
+ slack.MsgOptionText(message, false),
+ )
+ return err
+}
+
func atoi(a string) int {
i, _ := strconv.Atoi(a)
return i
diff --git a/internal/server/slack/slack.go b/internal/server/slack/slack.go
index 743693b2..f3f182e8 100644
--- a/internal/server/slack/slack.go
+++ b/internal/server/slack/slack.go
@@ -1,3 +1,9 @@
+// Copyright 2021 Gitploy.IO Inc. All rights reserved.
+// Use of this source code is governed by the Gitploy Non-Commercial License
+// that can be found in the LICENSE file.
+
+// +build !oss
+
package slack
import (
@@ -10,26 +16,6 @@ import (
"github.com/gitploy-io/gitploy/ent/callback"
"github.com/slack-go/slack"
"go.uber.org/zap"
- "golang.org/x/oauth2"
-)
-
-type (
- Slack struct {
- host string
- proto string
-
- c *oauth2.Config
- i Interactor
-
- log *zap.Logger
- }
-
- SlackConfig struct {
- ServerHost string
- ServerProto string
- *oauth2.Config
- Interactor
- }
)
const (
@@ -102,24 +88,3 @@ func (s *Slack) Interact(c *gin.Context) {
s.interactUnlock(c)
}
}
-
-func postResponseMessage(channelID, responseURL, message string) error {
- _, _, _, err := slack.
- New("").
- SendMessage(
- channelID,
- slack.MsgOptionResponseURL(responseURL, "ephemeral"),
- slack.MsgOptionText(message, false),
- )
- return err
-}
-
-func postBotMessage(cu *ent.ChatUser, message string) error {
- _, _, _, err := slack.
- New(cu.BotToken).
- SendMessage(
- cu.ID,
- slack.MsgOptionText(message, false),
- )
- return err
-}
diff --git a/internal/server/slack/slack_oss.go b/internal/server/slack/slack_oss.go
new file mode 100644
index 00000000..0b53b000
--- /dev/null
+++ b/internal/server/slack/slack_oss.go
@@ -0,0 +1,21 @@
+// +build oss
+
+package slack
+
+import (
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+)
+
+func NewSlack(c *SlackConfig) *Slack {
+ return &Slack{}
+}
+
+func (s *Slack) Cmd(c *gin.Context) {
+ c.Status(http.StatusInternalServerError)
+}
+
+func (s *Slack) Interact(c *gin.Context) {
+ c.Status(http.StatusInternalServerError)
+}
diff --git a/internal/server/slack/types.go b/internal/server/slack/types.go
new file mode 100644
index 00000000..a58aec81
--- /dev/null
+++ b/internal/server/slack/types.go
@@ -0,0 +1,25 @@
+package slack
+
+import (
+ "go.uber.org/zap"
+ "golang.org/x/oauth2"
+)
+
+type (
+ Slack struct {
+ host string
+ proto string
+
+ c *oauth2.Config
+ i Interactor
+
+ log *zap.Logger
+ }
+
+ SlackConfig struct {
+ ServerHost string
+ ServerProto string
+ *oauth2.Config
+ Interactor
+ }
+)
diff --git a/ui/src/models/errors.ts b/ui/src/models/errors.ts
index 2adf6942..bd4f1156 100644
--- a/ui/src/models/errors.ts
+++ b/ui/src/models/errors.ts
@@ -28,7 +28,7 @@ export class HttpPaymentRequiredError extends HttpRequestError {
constructor(public m: string) {
super(StatusCodes.PAYMENT_REQUIRED, m)
- Object.setPrototypeOf(this, HttpUnauthorizedError.prototype)
+ Object.setPrototypeOf(this, HttpPaymentRequiredError.prototype)
}
}
diff --git a/ui/src/redux/main.ts b/ui/src/redux/main.ts
index 9010687f..396f928d 100644
--- a/ui/src/redux/main.ts
+++ b/ui/src/redux/main.ts
@@ -1,4 +1,5 @@
import { createSlice, Middleware, MiddlewareAPI, isRejectedWithValue, PayloadAction, createAsyncThunk } from "@reduxjs/toolkit"
+import { message } from "antd"
import {
User,
@@ -56,7 +57,12 @@ export const apiMiddleware: Middleware = (api: MiddlewareAPI) => (
} else if (action.payload instanceof HttpInternalServerError) {
api.dispatch(mainSlice.actions.setAvailable(false))
} else if (action.payload instanceof HttpPaymentRequiredError) {
- api.dispatch(mainSlice.actions.setExpired(true))
+ // Only display the message to prevent damaging the user expirence.
+ if (process.env.REACT_APP_GITPLOY_OSS?.toUpperCase() === "TRUE") {
+ message.warn("Sorry, it is limited to the community edition.")
+ } else {
+ api.dispatch(mainSlice.actions.setExpired(true))
+ }
}
next(action)
diff --git a/ui/src/views/Main.tsx b/ui/src/views/Main.tsx
index 98d06082..08d018e8 100644
--- a/ui/src/views/Main.tsx
+++ b/ui/src/views/Main.tsx
@@ -43,6 +43,11 @@ export default function Main(props: any) {
}
}, [dispatch])
+ const onClickRetry = () => {
+ dispatch(slice.actions.setAvailable(true))
+ dispatch(slice.actions.setExpired(false))
+ }
+
const [ isRecentActivitiesVisible, setRecentActivitiesVisible ] = useState(false)
const showRecentActivities = () => {
@@ -63,6 +68,7 @@ export default function Main(props: any) {
status="error"
title="Server Internal Error"
subTitle="Sorry, something went wrong. Contact administrator."
+ extra={[]}
/>
} else if (!authorized) {
content = Retry]}
/>
} else {
content = props.children