skydum

個人的な作業記録とか備忘録代わりのメモ

vue.jsのdataに定義された変数を文字列で指定したい

vue.jsのdataに定義された変数を文字列で指定したい

  • 以下のように定義されているとする
  • この時にthis.firstValueを"this.firstValue"の様に文字で指定してthis.firstValueに値を入れたいとする
    <script>
        new Vue({
            el: "#app",
            vuetify: new Vuetify(),
            components: {},
            data: {
                firstValue: null,
                secondValue: null,
                insertData: [{ "key": "firstValue", "value": "foo" }, { "key": "secondValue", "value": "bar" }]
            },
            created() {
                this.insertData.forEach(element => {
                    const key = "this." + element.key
                    let v = eval(key);
                    v = element.value
                });
            },
            methods: {},
            computed: {},
            watch: {}
        })
    </script>

this.変数名はthis[変数名]で参照ができる

  • この様な場合vueでは以下のように書くことができる
  • this["firstValue]とするとthis.firstValueに値を入れることができる
    <script>
        new Vue({
            el: "#app",
            vuetify: new Vuetify(),
            components: {},
            data: {
                firstValue: null,
                secondValue: null,
                insertData: [{ "key": "firstValue", "value": "foo" }, { "key": "secondValue", "value": "bar" }]
            },
            created() {
                this.insertData.forEach(element => {
                    this[element.key] = element.value;
                });
            },
            methods: {},
            computed: {},
            watch: {}
        })
    </script>

ソース全文

ソース全文

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
    <link href="https://cdn.jsdelivr.net/npm/@mdi/font@6.x/css/materialdesignicons.min.css" rel="stylesheet">
    <link href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css" rel="stylesheet">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <v-app>
            <v-main>
                <v-container>
                    <v-card>
                        <v-card-title>dataの中の変数をthis+文字列で値を入れる</v-card-title>
                        <v-card-text>{{ firstValue }}</v-card-text>
                        <v-card-text>{{ secondValue }}</v-card-text>
                    </v-card>

                </v-container>
            </v-main>
            </v-main>
        </v-app>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue-router@3.5.3/dist/vue-router.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script src="https://unpkg.com/http-vue-loader"></script>
    <script>
        new Vue({
            el: "#app",
            vuetify: new Vuetify(),
            components: {},
            data: {
                firstValue: null,
                secondValue: null,
                insertData: [{ "key": "firstValue", "value": "foo" }, { "key": "secondValue", "value": "bar" }]
            },
            created() {
                this.insertData.forEach(element => {
                    this[element.key] = element.value;
                });
            },
            methods: {},
            computed: {},
            watch: {}
        })
    </script>
</body>

</html>

SQLAlchemyで外部制約キーを複数持ったリレーションシップの設定

SQLAlchemyを使って外部制約キーを複数持ったリレーションシップを設定する

リレーションシップの基本

構築したいDBのER図

ER図

生成したいSQL

CREATE TABLE persons (
        id VARCHAR(4) NOT NULL, 
        name VARCHAR(30) NOT NULL, 
        PRIMARY KEY (id)
)

CREATE TABLE tickets (
        person_id VARCHAR(4) NOT NULL, 
        ticket_id VARCHAR(8) NOT NULL, 
        room_name VARCHAR(30) NOT NULL, 
        PRIMARY KEY (person_id, ticket_id), 
        FOREIGN KEY(person_id) REFERENCES persons (id)
)

CREATE TABLE bookings (
        person_id VARCHAR(4) NOT NULL, 
        ticket_id VARCHAR(8) NOT NULL, 
        booking_id VARCHAR(12) NOT NULL, 
        comment VARCHAR(30), 
        PRIMARY KEY (person_id, ticket_id, booking_id), 
        FOREIGN KEY(person_id, ticket_id) REFERENCES tickets (person_id, ticket_id)
)

環境

  • Python 3.8.10
  • SQLAlchemy == 1.4.27

基本的なリレーションシップのパターン

ER図

ER図基本パターン

python

  • Personクラスからrelation_shipでTicketクラスにリレーションを貼る
  • Person側のrelation_shipにback_refがついているのでTicket側にはrelation_shinpの設定をしなくてもリレーションが貼られる
  • Personのtickets = relationship("Ticket", backref="persons")の様にする

pythonのソース(全文)

from sqlalchemy import VARCHAR, Column, ForeignKey, MetaData, UniqueConstraint, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, scoped_session, sessionmaker

url = "sqlite:///"
engine = create_engine(url, echo=False)
Session = scoped_session(sessionmaker(bind=engine, autocommit=False, autoflush=False))
Base = declarative_base()


class Person(Base):
    __tablename__ = "persons"

    id = Column(VARCHAR(4), autoincrement=False, primary_key=True)
    name = Column(VARCHAR(30), nullable=False)

    tickets = relationship("Ticket", backref="persons")

    def dict(self):
        tickets = [ticket.dict() for ticket in self.tickets]

        res = {"id": self.id, "name": self.name, "tickets": tickets}
        return res


class Ticket(Base):
    __tablename__ = "tickets"

    person_id = Column(VARCHAR(4), ForeignKey("persons.id"), primary_key=True, autoincrement=False)
    ticket_id = Column(VARCHAR(8), primary_key=True, autoincrement=False)
    room_name = Column(VARCHAR(30), nullable=False)

    def dict(self):
        res = {
            "person_id": self.person_id,
            "ticket_id": self.ticket_id,
            "room_name": self.room_name,
        }
        return res


metadata = MetaData()


Base.metadata.create_all(bind=engine, checkfirst=True)


def insert_data():
    with Session() as session:
        person = Person(id=1, name="hoge")
        tickets = [
            Ticket(person_id="1000", ticket_id="10001000", room_name="hoge"),
            Ticket(person_id="1000", ticket_id="10001001", room_name="hage"),
        ]

        person.tickets.extend(tickets)

        session.add(person)
        session.commit()


def query():
    import json

    with Session() as session:
        session = Session()
        res = session.query(Person).one()

        print(json.dumps(res.dict(), indent=2))

        res = session.query(Ticket).filter(Ticket.ticket_id == "10001000").one()
        print(json.dumps(res.dict(), indent=2))
        print(json.dumps(res.persons.dict(), indent=2))


if __name__ == "__main__":
    insert_data()
    query()

実行結果

# print(json.dumps(res.dict(), indent=2))
{
  "id": "1",
  "name": "hoge",
  "tickets": [
    {
      "person_id": "1",
      "ticket_id": "10001000",
      "room_name": "hoge"
    },
    {
      "person_id": "1",
      "ticket_id": "10001001",
      "room_name": "hage"
    }
  ]
}
# print(json.dumps(res.dict(), indent=2))
{
  "person_id": "1",
  "ticket_id": "10001000",
  "room_name": "hoge"
}
# リレーションを参照して親(Person)側から再度データを取得している
# print(json.dumps(res.persons.dict(), indent=2))
{
  "id": "1",
  "name": "hoge",
  "tickets": [
    {
      "person_id": "1",
      "ticket_id": "10001000",
      "room_name": "hoge"
    },
    {
      "person_id": "1",
      "ticket_id": "10001001",
      "room_name": "hage"
    }
  ]
}

拡張したパターン

ER図(最初に貼ったのと同じ)

ER図(最初と同じ)

python

  • ticketテーブルのときは外部制約に利用するperson_idにはForeignkeyをつけたけれどもbookinsの様に外部制約キーが2個ある場合はテーブルのColumnの方にはForeignKeyをつけずに table_args = (ForeignKeyConstraint(["person_id", "ticket_id"], ["tickets.person_id", "tickets.ticket_id"]),)の様にする

bookingとticketの双方向のリレーションを構築する

tikect → booking
  bookings = relationship(
        "Booking", foreign_keys="[Booking.person_id, Booking.ticket_id]", back_populates="ticket_back_populates"
    )
booking → ticket
  __table_args__ = (ForeignKeyConstraint(["person_id", "ticket_id"], ["tickets.person_id", "tickets.ticket_id"]),)

  ticket_back_populates = relationship(
        "Ticket", foreign_keys="[Booking.person_id, Booking.ticket_id]", back_populates="bookings"
    )

ソースコード

pythonのソース(全文)

from sqlalchemy import VARCHAR, Column, ForeignKey, ForeignKeyConstraint, MetaData, UniqueConstraint, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, scoped_session, sessionmaker

url = "sqlite:///"
engine = create_engine(url, echo=False)
Session = scoped_session(sessionmaker(bind=engine, autocommit=False, autoflush=False))
Base = declarative_base()


class Person(Base):
    __tablename__ = "persons"

    id = Column(VARCHAR(4), autoincrement=False, primary_key=True)
    name = Column(VARCHAR(30), nullable=False)

    tickets = relationship("Ticket", backref="persons")

    def dict(self):
        tickets = [ticket.dict() for ticket in self.tickets]

        res = {"id": self.id, "name": self.name, "tickets": tickets}
        return res


class Ticket(Base):
    __tablename__ = "tickets"

    person_id = Column(VARCHAR(4), ForeignKey("persons.id"), primary_key=True, autoincrement=False)
    ticket_id = Column(VARCHAR(8), primary_key=True, autoincrement=False)
    room_name = Column(VARCHAR(30), nullable=False)
    bookings = relationship(
        "Booking", foreign_keys="[Booking.person_id, Booking.ticket_id]", back_populates="ticket_back_populates"
    )

    def dict(self):
        bookings = [booking.dict() for booking in self.bookings]

        res = {
            "person_id": self.person_id,
            "ticket_id": self.ticket_id,
            "room_name": self.room_name,
            "bookings": bookings,
        }
        return res


class Booking(Base):
    __tablename__ = "bookings"
    __table_args__ = (ForeignKeyConstraint(["person_id", "ticket_id"], ["tickets.person_id", "tickets.ticket_id"]),)

    person_id = Column(VARCHAR(4), primary_key=True, autoincrement=False)
    ticket_id = Column(VARCHAR(8), primary_key=True, autoincrement=False)
    booking_id = Column(VARCHAR(12), primary_key=True, autoincrement=False)
    comment = Column(VARCHAR(30), nullable=True)

    ticket_back_populates = relationship(
        "Ticket", foreign_keys="[Booking.person_id, Booking.ticket_id]", back_populates="bookings"
    )

    def dict(self):
        res = {
            "person_id": self.person_id,
            "ticket_id": self.ticket_id,
            "booking_id": self.booking_id,
            "comment": self.comment,
        }
        return res


metadata = MetaData()
metadata.reflect(engine)


Base.metadata.create_all(bind=engine, checkfirst=True)


def insert_data():
    with Session() as session:
        person = Person(id=1, name="hoge")
        tickets = [
            Ticket(person_id="1000", ticket_id="10001000", room_name="hoge"),
            Ticket(person_id="1000", ticket_id="10001001", room_name="hage"),
        ]
        booking1 = Booking(person_id="1000", ticket_id="10001000", booking_id="100010001000", comment="first ticket")
        tickets[0].bookings.append(booking1)

        booking2 = [
            Booking(person_id="1000", ticket_id="10001001", booking_id="100010011000", comment="first ticket"),
            Booking(person_id="1000", ticket_id="10001001", booking_id="100010011001", comment="second ticket"),
        ]
        tickets[1].bookings.extend(booking2)

        person.tickets.extend(tickets)

        session.add(person)
        session.commit()


def query():
    import json

    with Session() as session:
        session = Session()
        res = session.query(Person).one()

        print(json.dumps(res.dict(), indent=2))

        res = session.query(Ticket).filter(Ticket.ticket_id == "10001000").one()
        print(json.dumps(res.dict(), indent=2))
        print(json.dumps(res.persons.dict(), indent=2))

        res = session.query(Booking).filter(Booking.booking_id == "100010011000").one()
        print(json.dumps(res.dict(), indent=2))
        print(json.dumps(res.ticket_back_populates.dict(), indent=2))
        print(json.dumps(res.ticket_back_populates.persons.dict(), indent=2))


if __name__ == "__main__":
    insert_data()
    query()

実行結果

# print(json.dumps(res.dict(), indent=2))

{
  "id": "1",
  "name": "hoge",
  "tickets": [
    {
      "person_id": "1",
      "ticket_id": "10001000",
      "room_name": "hoge",
      "bookings": [
        {
          "person_id": "1",
          "ticket_id": "10001000",
          "booking_id": "100010001000",
          "comment": "first ticket"
        }
      ]
    },
    {
      "person_id": "1",
      "ticket_id": "10001001",
      "room_name": "hage",
      "bookings": [
        {
          "person_id": "1",
          "ticket_id": "10001001",
          "booking_id": "100010011000",
          "comment": "first ticket"
        },
        {
          "person_id": "1",
          "ticket_id": "10001001",
          "booking_id": "100010011001",
          "comment": "second ticket"
        }
      ]
    }
  ]
}
# print(json.dumps(res.dict(), indent=2))

{
  "person_id": "1",
  "ticket_id": "10001000",
  "room_name": "hoge",
  "bookings": [
    {
      "person_id": "1",
      "ticket_id": "10001000",
      "booking_id": "100010001000",
      "comment": "first ticket"
    }
  ]
}
# print(json.dumps(res.persons.dict(), indent=2))

{
  "id": "1",
  "name": "hoge",
  "tickets": [
    {
      "person_id": "1",
      "ticket_id": "10001000",
      "room_name": "hoge",
      "bookings": [
        {
          "person_id": "1",
          "ticket_id": "10001000",
          "booking_id": "100010001000",
          "comment": "first ticket"
        }
      ]
    },
    {
      "person_id": "1",
      "ticket_id": "10001001",
      "room_name": "hage",
      "bookings": [
        {
          "person_id": "1",
          "ticket_id": "10001001",
          "booking_id": "100010011000",
          "comment": "first ticket"
        },
        {
          "person_id": "1",
          "ticket_id": "10001001",
          "booking_id": "100010011001",
          "comment": "second ticket"
        }
      ]
    }
  ]
}
# print(json.dumps(res.dict(), indent=2))

{
  "person_id": "1",
  "ticket_id": "10001001",
  "booking_id": "100010011000",
  "comment": "first ticket"
}
# print(json.dumps(res.ticket_back_populates.dict(), indent=2))

{
  "person_id": "1",
  "ticket_id": "10001001",
  "room_name": "hage",
  "bookings": [
    {
      "person_id": "1",
      "ticket_id": "10001001",
      "booking_id": "100010011000",
      "comment": "first ticket"
    },
    {
      "person_id": "1",
      "ticket_id": "10001001",
      "booking_id": "100010011001",
      "comment": "second ticket"
    }
  ]
}
#  print(json.dumps(res.ticket_back_populates.persons.dict(), indent=2))

{
  "id": "1",
  "name": "hoge",
  "tickets": [
    {
      "person_id": "1",
      "ticket_id": "10001000",
      "room_name": "hoge",
      "bookings": [
        {
          "person_id": "1",
          "ticket_id": "10001000",
          "booking_id": "100010001000",
          "comment": "first ticket"
        }
      ]
    },
    {
      "person_id": "1",
      "ticket_id": "10001001",
      "room_name": "hage",
      "bookings": [
        {
          "person_id": "1",
          "ticket_id": "10001001",
          "booking_id": "100010011000",
          "comment": "first ticket"
        },
        {
          "person_id": "1",
          "ticket_id": "10001001",
          "booking_id": "100010011001",
          "comment": "second ticket"
        }
      ]
    }
  ]
}

おまけ

  • 複数のカラムにuniq制約をつける

  • sqlalchemyのテーブル定義tips qiita.com

構築したいDBのサンプルにuniqを追加したパターン

python(全文)

from sqlalchemy import VARCHAR, Column, ForeignKey, ForeignKeyConstraint, MetaData, UniqueConstraint, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, scoped_session, sessionmaker

url = "sqlite:///"
engine = create_engine(url, echo=False)
Session = scoped_session(sessionmaker(bind=engine, autocommit=False, autoflush=False))
Base = declarative_base()


class Person(Base):
    __tablename__ = "persons"

    id = Column(VARCHAR(4), autoincrement=False, primary_key=True)
    name = Column(VARCHAR(30), nullable=False)

    tickets = relationship("Ticket", backref="persons")

    def dict(self):
        tickets = [ticket.dict() for ticket in self.tickets]

        res = {"id": self.id, "name": self.name, "tickets": tickets}
        return res


class Ticket(Base):
    __tablename__ = "tickets"
    __table_args__ = (UniqueConstraint("person_id", "ticket_id", name="uniq_person_id_ticket_id"),)

    person_id = Column(VARCHAR(4), ForeignKey("persons.id"), primary_key=True, autoincrement=False)
    ticket_id = Column(VARCHAR(8), primary_key=True, autoincrement=False)
    room_name = Column(VARCHAR(30), nullable=False)
    bookings = relationship(
        "Booking", foreign_keys="[Booking.person_id, Booking.ticket_id]", back_populates="ticket_back_populates"
    )

    def dict(self):
        bookings = [booking.dict() for booking in self.bookings]

        res = {
            "person_id": self.person_id,
            "ticket_id": self.ticket_id,
            "room_name": self.room_name,
            "bookings": bookings,
        }
        return res


class Booking(Base):
    __tablename__ = "bookings"
    __table_args__ = (
        UniqueConstraint("person_id", "ticket_id", "booking_id", name="uniq_person_id_ticket_id_booking_id"),
        ForeignKeyConstraint(["person_id", "ticket_id"], ["tickets.person_id", "tickets.ticket_id"]),
    )

    person_id = Column(VARCHAR(4), primary_key=True, autoincrement=False)
    ticket_id = Column(VARCHAR(8), primary_key=True, autoincrement=False)
    booking_id = Column(VARCHAR(12), primary_key=True, autoincrement=False)
    comment = Column(VARCHAR(30), nullable=True)

    ticket_back_populates = relationship(
        "Ticket", foreign_keys="[Booking.person_id, Booking.ticket_id]", back_populates="bookings"
    )

    def dict(self):
        res = {
            "person_id": self.person_id,
            "ticket_id": self.ticket_id,
            "booking_id": self.booking_id,
            "comment": self.comment,
        }
        return res


metadata = MetaData()
metadata.reflect(engine)


Base.metadata.create_all(bind=engine, checkfirst=True)


def insert_data():
    with Session() as session:
        person = Person(id=1, name="hoge")
        tickets = [
            Ticket(person_id="1000", ticket_id="10001000", room_name="hoge"),
            Ticket(person_id="1000", ticket_id="10001001", room_name="hage"),
        ]
        booking1 = Booking(person_id="1000", ticket_id="10001000", booking_id="100010001000", comment="first ticket")
        tickets[0].bookings.append(booking1)

        booking2 = [
            Booking(person_id="1000", ticket_id="10001001", booking_id="100010011000", comment="first ticket"),
            Booking(person_id="1000", ticket_id="10001001", booking_id="100010011001", comment="second ticket"),
        ]
        tickets[1].bookings.extend(booking2)

        person.tickets.extend(tickets)

        session.add(person)
        session.commit()


def query():
    import json

    with Session() as session:
        session = Session()
        res = session.query(Person).one()

        print(json.dumps(res.dict(), indent=2))

        res = session.query(Ticket).filter(Ticket.ticket_id == "10001000").one()
        print(json.dumps(res.dict(), indent=2))
        print(json.dumps(res.persons.dict(), indent=2))

        res = session.query(Booking).filter(Booking.booking_id == "100010011000").one()
        print(json.dumps(res.dict(), indent=2))
        print(json.dumps(res.ticket_back_populates.dict(), indent=2))
        print(json.dumps(res.ticket_back_populates.persons.dict(), indent=2))


if __name__ == "__main__":
    insert_data()
    query()

オフライン環境でDockerを使ってGrowiを構築する

オフライン環境でGrowi(Docker)を利用して構築する

想定する環境

  • インターネットに接続できる環境がある
  • Growiを構築する環境にはインターネットへの接続不可

構築する環境

  1. Growi 4.4.9
  2. Growiをオフライン環境で利用する
  3. ファイルアップロード先はローカル
  4. 連携するサービス
    • PLANTUML
    • HACKMD

構築するサーバのIPアドレスとポート番号

サービス名 IPアドレス:ポート番号
Growi 192.168.0.251:3000
PLANTUML 192.168.0.251:8080
HACKMD 192.168.0.251:3100

最終的なファイル構成

growi
├── docker-compose.override.yml                 # ./examples/integrate-with-hackmd/docker-compose.override.ymlをコピー
├── docker-compose.yml
├── download-frozen-image-v2.sh                 # ダウンロード
├── elasticsearch
│   ├── Dockerfile
│   ├── analysis-icu-6.8.10.zip                 # ダウンロード
│   ├── analysis-kuromoji-6.8.10.zip            # ダウンロード
│   ├── config                                  # growi-docker-composeをクローンしてきたディレクトリからコピー
│   │   ├── elasticsearch.yml         
│   │   └── log4j2.properties
│   └── docker-compose.yml
├── growiapp
│   ├── Dockerfile                              # ./Dockerfileをコピー
│   └── dockerize-linux-amd64-v0.6.1.tar.gz     # ダウンロード
└── hackmd
    ├── Dockerfile
    ├── apply-growi-agent.sh
    └── config.json
  • 2021/11/15 ./elasticsearch/config以下のファイルの入手先を追記

ダウンロードが必要なファイルのまとめ

  • まとめてファイルをダウンロードしたい人向け
git clone https://github.com/weseek/growi-docker-compose.git
curl https://raw.githubusercontent.com/moby/moby/master/contrib/download-frozen-image-v2.sh -O

/bin/bash ./download-frozen-image-v2.sh growi_4.4.9-nocdn weseek/growi:4.4.9-nocdn
/bin/bash ./download-frozen-image-v2.sh elasticsearch_6.8.10 elasticsearch:6.8.10
/bin/bash ./download-frozen-image-v2.sh mongo_4.4 mongo:4.4
/bin/bash ./download-frozen-image-v2.sh hackmd_1.3.0-alpine hackmdio/hackmd:1.3.0-alpine
/bin/bash ./download-frozen-image-v2.sh mariadb_10.3 mariadb:10.3
/bin/bash ./download-frozen-image-v2.sh jetty_v1.2021.12 plantuml/plantuml-server:jetty-v1.2021.12

curl -L https://github.com/jwilder/dockerize/releases/download/v0.6.1/dockerize-linux-amd64-v0.6.1.tar.gz -O

curl https://artifacts.elastic.co/downloads/elasticsearch-plugins/analysis-icu/analysis-icu-6.8.10.zip -O
curl https://artifacts.elastic.co/downloads/elasticsearch-plugins/analysis-kuromoji/analysis-kuromoji-6.8.10.zip -O
  • 2021/11/15 dockerizeをダウンロードしようとした時に302でリダイレクトがされる為、ファイルのダウンロードが失敗するため-Lオプションを追加

構築手順

  1. growi-docker-composeをgit clone
  2. Docker Hubからコンテナのダウンロード
    • 2-1. Dockerコマンドが利用できない場合
    • 2-2. Dockerコマンドが利用できる場合
    • 2-3. 共通
  3. 必要なファイルのダウンロード
  4. docker-compose.yamlの変更
  5. docker-compose-override.yamlの変更
  6. docker-compose up -dで起動

1. growi-docker-composeをgit clone

git clone https://github.com/weseek/growi-docker-compose.git

2-1. Dockerコンテナのダウンロード(Dockerコマンドが利用できない場合)

Docker imageのダウンロードツール(Dockerコマンドが利用できない場合)

curl https://raw.githubusercontent.com/moby/moby/master/contrib/download-frozen-image-v2.sh -O

Dockerコンテナのダウンロード(Dockerコマンドが利用できない場合)

  • Docker HubからGrowiを構築する際に利用するコンテナのイメージを取得する
    • Growi 4.4.9 NO CDN
    • Elasticsearch 6.8.10
    • MongoDB 4.4
    • HACKMD 1.3.0(alpine)
    • MariaDB 10.3
    • PLANTUML V1.2021.12
/bin/bash ./download-frozen-image-v2.sh growi_4.4.9-nocdn weseek/growi:4.4.9-nocdn
/bin/bash ./download-frozen-image-v2.sh elasticsearch_6.8.10 elasticsearch:6.8.10
/bin/bash ./download-frozen-image-v2.sh mongo_4.4 mongo:4.4

/bin/bash ./download-frozen-image-v2.sh hackmd_1.3.0-alpine hackmdio/hackmd:1.3.0-alpine
/bin/bash ./download-frozen-image-v2.sh mariadb_10.3 mariadb:10.3

/bin/bash ./download-frozen-image-v2.sh jetty_v1.2021.12 plantuml/plantuml-server:jetty-v1.2021.12

コンテナをオフライン環境へ移行する(Dockerコマンドが利用できない場合)

  • 取得したコンテナイメージをオフライン環境へ移行する為にtarでアーカイブする
tar -C growi_4.4.9-nocdn -cf growi_4.4.9-nocdn.tar .
tar -C elasticsearch_6.8.10 -cf elasticsearch_6.8.10.tar .
tar -C mongo_4.4 -cf mongo_4.4.tar .

tar -C hackmd_1.3.0-alpine -cf hackmd_1.3.0-alpine.tar .
tar -C mariadb_10.3 -cf mariadb_10.3.tar .

tar -C jetty_v1.2021.12 -cf jetty_v1.2021.12.tar .

2-2. Dockerコンテナのダウンロード(Dockerコマンド利用)

Dockerコンテナのダウンロード(Dockerコマンド利用)

docker pull weseek/growi:4.4.9-nocdn
docker pull elasticsearch:6.8.10
docker pull mongo:4.4
docker pull hackmdio/hackmd:1.3.0-alpine
docker pull mariadb:10.3
docker pull plantuml/plantuml-server:jetty-v1.2021.12

コンテナをオフライン環境へ移行する(Dockerコマンド利用)

docker save weseek/growi -o growi_4.4.9-nocdn.tar
docker save elasticsearch -o elasticsearch_6.8.10.tar
docker save mongo -o mongo_4.4.tar
docker save hackmdio/hackmd -o hackmd_1.3.0-alpine.tar
docker save mariadb -o mariadb_10.3.tar
docker save plantuml/plantuml-server -o jetty_v1.2021.12.tar

2-3. Docker imageをdockerへインポートする(共通)

  • オフライン環境へ移行したイメージをdocker loadでコンテナイメージを取り込む
docker load -i growi_4.4.9-nocdn.tar
docker load -i elasticsearch_6.8.10.tar 
docker load -i mongo_4.4.tar 

docker load -i hackmd_1.3.0-alpine.tar 
docker load -i mariadb_10.3.tar

docker load -i jetty_v1.2021.12.tar

3. 必要なファイルのダウンロード

Growi

  1. growiappディレクトリを作成
  2. growiディレクトリ直下のDockerファイルをgrowiappディレクトリへ移動する

Dockerfileの変更

  • 変更前
FROM weseek/growi:4
LABEL maintainer Yuki Takei <yuki@weseek.co.jp>

# install dockerize
ENV DOCKERIZE_VERSION v0.6.1
USER root
RUN apt-get update && apt-get install -y curl \
    && curl -sL https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
        | tar -xz -C /usr/local/bin \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*
  • 変更後
FROM weseek/growi:4.4.9-nocdn
LABEL maintainer Yuki Takei <yuki@weseek.co.jp>

# install dockerize
# ENV DOCKERIZE_VERSION v0.6.1
USER root

ADD ./dockerize-linux-amd64-v0.6.1.tar.gz /usr/local/bin

dockerize-linux-amd64-v0.6.1.tar.gz のダウンロード

  • 変更前のDockerfileにダウンロードするバージョンの記載(ENV DOCKERIZE_VERSION v0.6.1)があるので合わせたものをダウンロードする
  • ダウンロードしたファイルはgrowiappのDockerfileと同じディレクトリに配置する
curl https://github.com/jwilder/dockerize/releases/download/v0.6.1/dockerize-linux-amd64-v0.6.1.tar.gz -O

Elasticsearch

  • elasticsearch/Dockerfileを書き換えてローカルからelasticsearchのプラグインの読み込みができるようにする
  • elasticsearch/config以下のファイルについてはgit cloneしたgrowi-docker-composeからコピーしてください

Dockerfileを書き換え

  • 変更前
FROM docker.elastic.co/elasticsearch/elasticsearch:6.8.10
LABEL maintainer Yuki Takei <yuki@weseek.co.jp>

RUN bin/elasticsearch-plugin install analysis-kuromoji
RUN bin/elasticsearch-plugin install analysis-icu
  • 変更後
    • ローカルに置いたファイルを利用してプラグインを組み込む
FROM elasticsearch:6.8.10
LABEL maintainer Yuki Takei <yuki@weseek.co.jp>

WORKDIR /usr/share/elasticsearch/

COPY ./analysis-kuromoji-6.8.10.zip /tmp
COPY ./analysis-icu-6.8.10.zip /tmp

RUN bin/elasticsearch-plugin install file:///tmp/analysis-kuromoji-6.8.10.zip
RUN bin/elasticsearch-plugin install file:///tmp/analysis-icu-6.8.10.zip
RUN rm -f /tmp/*.zip

pluginのダウンロード

  • ダウンロードしたプラグインをElasticsearchのDockerfileと同じディレクトリに配置する
    • Dockerファイルに書いたelasticsearchのバージョンと合わせたファイルをダウンロードすること
curl https://artifacts.elastic.co/downloads/elasticsearch-plugins/analysis-icu/analysis-icu-6.8.10.zip -O
curl https://artifacts.elastic.co/downloads/elasticsearch-plugins/analysis-kuromoji/analysis-kuromoji-6.8.10.zip -O

4. docker-compose.yamlの変更

  • Growiを構築する環境に合わせて変更を行う

変更前と変更後の全文

変更前

version: '3'

services:
  app:
    build:
      context: .
      dockerfile: ./Dockerfile
    ports:
      - 127.0.0.1:3000:3000    # localhost only by default
    links:
      - mongo:mongo
      - elasticsearch:elasticsearch
    depends_on:
      - mongo
      - elasticsearch
    environment:
      - MONGO_URI=mongodb://mongo:27017/growi
      - ELASTICSEARCH_URI=http://elasticsearch:9200/growi
      - PASSWORD_SEED=changeme
      # - FILE_UPLOAD=mongodb   # activate this line if you use MongoDB GridFS rather than AWS
      # - FILE_UPLOAD=local     # activate this line if you use local storage of server rather than AWS
      # - MATHJAX=1             # activate this line if you want to use MathJax
      # - PLANTUML_URI=http://  # activate this line and specify if you use your own PlantUML server rather than public plantuml.com
      # - HACKMD_URI=http://    # activate this line and specify HackMD server URI which can be accessed from GROWI client browsers
      # - HACKMD_URI_FOR_SERVER=http://hackmd:3000  # activate this line and specify HackMD server URI which can be accessed from this server container
      # - FORCE_WIKI_MODE='public'    # activate this line to force wiki public mode
      # - FORCE_WIKI_MODE='private'   # activate this line to force wiki private mode

    entrypoint: "dockerize
                  -wait tcp://mongo:27017
                  -wait tcp://elasticsearch:9200
                  -timeout 60s
                  /docker-entrypoint.sh"
    command: ["yarn migrate && node -r dotenv-flow/config --expose_gc dist/server/app.js"]

    restart: unless-stopped
    volumes:
      - growi_data:/data

  mongo:
    image: mongo:4.4
    restart: unless-stopped
    volumes:
      - mongo_configdb:/data/configdb
      - mongo_db:/data/db

  elasticsearch:
    build:
      context: ./elasticsearch
      dockerfile: ./Dockerfile
    environment:
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms256m -Xmx256m"  # increase amount if you have enough memory
    ulimits:
      memlock:
        soft: -1
        hard: -1
    restart: unless-stopped
    volumes:
      - es_data:/usr/share/elasticsearch/data
      - ./elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml

volumes:
  growi_data:
  mongo_configdb:
  mongo_db:
  es_data:

変更後

version: '3'

services:
  app:
    build:
      context: ./growiapp
      dockerfile: ./Dockerfile
    ports:
      - 3000:3000    # localhost only by default
    links:
      - mongo:mongo
      - elasticsearch:elasticsearch
    depends_on:
      - mongo
      - elasticsearch
    environment:
      - MONGO_URI=mongodb://mongo:27017/growi
      - ELASTICSEARCH_URI=http://elasticsearch:9200/growi
      - PASSWORD_SEED=changeme
      # - FILE_UPLOAD=mongodb   # activate this line if you use MongoDB GridFS rather than AWS
      - FILE_UPLOAD=local     # activate this line if you use local storage of server rather than AWS
      # - MATHJAX=1             # activate this line if you want to use MathJax
      - PLANTUML_URI=http://192.168.0.251:8080  # activate this line and specify if you use your own PlantUML server rather than public plantuml.com
      - HACKMD_URI=http://192.168.0.251:3100    # activate this line and specify HackMD server URI which can be accessed from GROWI client browsers
      - HACKMD_URI_FOR_SERVER=http://hackmd:3000  # activate this line and specify HackMD server URI which can be accessed from this server container
      # - FORCE_WIKI_MODE='public'    # activate this line to force wiki public mode
      # - FORCE_WIKI_MODE='private'   # activate this line to force wiki private mode

    entrypoint: "dockerize
                  -wait tcp://mongo:27017
                  -wait tcp://elasticsearch:9200
                  -timeout 60s
                  /docker-entrypoint.sh"
    command: ["yarn migrate && node -r dotenv-flow/config --expose_gc dist/server/app.js"]

    restart: unless-stopped
    volumes:
      - growi_data:/data

  mongo:
    image: mongo:4.4
    restart: unless-stopped
    volumes:
      - mongo_configdb:/data/configdb
      - mongo_db:/data/db

  elasticsearch:
    build:
      context: ./elasticsearch
      dockerfile: ./Dockerfile
    environment:
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms256m -Xmx256m"  # increase amount if you have enough memory
    ulimits:
      memlock:
        soft: -1
        hard: -1
    restart: unless-stopped
    volumes:
      - es_data:/usr/share/elasticsearch/data
      - ./elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml

volumes:
  growi_data:
  mongo_configdb:
  mongo_db:
  es_data:

変更前と変更後の差分

  • 変更の差分
    • growiの構築に利用するDockerファイルをgrowiapp以下のものを利用するように変更
  # 変更前
  app:
    build:
      context: .
      dockerfile: ./Dockerfile

  # 変更後
  app:
    build:
      context: ./growiapp
      dockerfile: ./Dockerfile
  • 外部からの接続を許可
    # 変更前
    ports:
      - 127.0.0.1:3000:3000    # localhost only by default

    # 変更後
    ports:
      - 3000:3000    # localhost only by default
  • PASSWORD_SEED, FILE_UPLOAD, PLANTUML_URI, HACKMD_URI, HACKMD_URI_FOR_SERVERの値を変更する
    # 変更前
    environment:
      - MONGO_URI=mongodb://mongo:27017/growi
      - ELASTICSEARCH_URI=http://elasticsearch:9200/growi
      - PASSWORD_SEED=changeme
      # - FILE_UPLOAD=mongodb   # activate this line if you use MongoDB GridFS rather than AWS
      # - FILE_UPLOAD=local     # activate this line if you use local storage of server rather than AWS
      # - MATHJAX=1             # activate this line if you want to use MathJax
      # - PLANTUML_URI=http://  # activate this line and specify if you use your own PlantUML server rather than public plantuml.com
      # - HACKMD_URI=http://    # activate this line and specify HackMD server URI which can be accessed from GROWI client browsers
      # - HACKMD_URI_FOR_SERVER=http://hackmd:3000  # activate this line and specify HackMD server URI which can be accessed from this server container
      # - FORCE_WIKI_MODE='public'    # activate this line to force wiki public mode
      # - FORCE_WIKI_MODE='private'   # activate this line to force wiki private mode

    # 変更後
    environment:
      - MONGO_URI=mongodb://mongo:27017/growi
      - ELASTICSEARCH_URI=http://elasticsearch:9200/growi
      - PASSWORD_SEED=changeme
      - FILE_UPLOAD=local     # activate this line if you use local storage of server rather than AWS
      - PLANTUML_URI=http://192.168.0.251:8080  # activate this line and specify if you use your own PlantUML server rather than public plantuml.com
      - HACKMD_URI=http://192.168.0.251:3100    # activate this line and specify HackMD server URI which can be accessed from GROWI client browsers
      - HACKMD_URI_FOR_SERVER=http://hackmd:3000  # activate this line and specify HackMD server URI which can be accessed from this server container

5. docker-compose-override.yamlの変更

  • examples/integrate-with-hackmd/docker-compose.override.ymlをgrowi配下へコピーして変更を行う

変更前と変更後の全文

変更前

version: '3'

services:
  ##
  # HackMD(CodiMD) container
  # see https://github.com/hackmdio/codimd#configuration
  #
  hackmd:
    build:
      context: ./hackmd
    environment:
      - GROWI_URI=http://CHANGE-HERE
      - CMD_DB_URL=mysql://hackmd:hackmdpass@mariadb:3306/hackmd
      - CMD_CSP_ENABLE=false
    ports:
      - 127.0.0.1:3100:3000   # localhost only by default
    depends_on:
      - mariadb
    restart: unless-stopped

  ##
  # MariaDB
  # see https://hub.docker.com/_/mariadb/
  mariadb:
    image: mariadb:10.3
    command: mysqld --character-set-server=utf8 --collation-server=utf8_general_ci
    environment:
      - MYSQL_USER=hackmd
      - MYSQL_PASSWORD=hackmdpass
      - MYSQL_DATABASE=hackmd
      - MYSQL_RANDOM_ROOT_PASSWORD=true
    restart: unless-stopped
    volumes:
      - mariadb_data:/var/lib/mysql

volumes:
  mariadb_data:

変更後

version: '3'

services:
  ##
  # HackMD(CodiMD) container
  # see https://github.com/hackmdio/codimd#configuration
  #
  hackmd:
    build:
      context: ./hackmd
    environment:
      - GROWI_URI=http://192.168.0.251:3000
      - CMD_DB_URL=mysql://hackmd:hackmdpass@mariadb:3306/hackmd
      - CMD_CSP_ENABLE=false
      - CMD_USECDN=false
      - CMD_PLANTUML_SERVER=http://192.168.0.251:8080
    ports:
      - 3100:3000   # localhost only by default
    depends_on:
      - mariadb
    restart: unless-stopped

  ##
  # MariaDB
  # see https://hub.docker.com/_/mariadb/
  mariadb:
    image: mariadb:10.3
    command: mysqld --character-set-server=utf8 --collation-server=utf8_general_ci
    environment:
      - MYSQL_USER=hackmd
      - MYSQL_PASSWORD=hackmdpass
      - MYSQL_DATABASE=hackmd
      - MYSQL_RANDOM_ROOT_PASSWORD=true
    restart: unless-stopped
    volumes:
      - mariadb_data:/var/lib/mysql

  plantuml:
    image: plantuml/plantuml-server:jetty-v1.2021.12
    restart: unless-stopped
    ports:
      - "8080:8080"

volumes:
  mariadb_data:


変更前と変更後の差分

  • HACKMDがCDNを利用しないオプションを追加、外部からの接続を許可、HACKMDとPLANTUMLの連携を追加
# 変更前
services:
  hackmd:
    build:
      context: ./hackmd
    environment:
      - GROWI_URI=http://CHANGE-HERE
      - CMD_DB_URL=mysql://hackmd:hackmdpass@mariadb:3306/hackmd
      - CMD_CSP_ENABLE=false
    ports:
      - 127.0.0.1:3100:3000   # localhost only by default
    depends_on:
      - mariadb
    restart: unless-stopped


# 変更後
  hackmd:
    build:
      context: ./hackmd
    environment:
      - GROWI_URI=http://192.168.0.251:3000
      - CMD_DB_URL=mysql://hackmd:hackmdpass@mariadb:3306/hackmd
      - CMD_CSP_ENABLE=false
      - CMD_USECDN=false
      - CMD_PLANTUML_SERVER=http://192.168.0.251:8080
    ports:
      - 3100:3000   # localhost only by default
    depends_on:
      - mariadb
    restart: unless-stopped
  • PLANTUMLを新規で追加
  plantuml:
    image: plantuml/plantuml-server:jetty-v1.2021.12
    restart: unless-stopped
    ports:
      - "8080:8080"

6. docker-compose up -dで起動

  • ./docker-compose.yamlファイルのある場所でdocker-compose up -dを実行
docker-compose up -d
  • 以下のように表示されていたら正常起動している
  • elasticsearchが異常終了した場合はelasticsearchがcode78 exitするときを参照して設定を変更してください
docker-compose ps

                Name                              Command               State                    Ports
------------------------------------------------------------------------------------------------------------------------
growi-docker-compose_app_1             dockerize -wait tcp://mong ...   Up      0.0.0.0:3000->3000/tcp,:::3000->3000/tcp
growi-docker-compose_elasticsearch_1   /usr/local/bin/docker-entr ...   Up      9200/tcp, 9300/tcp
growi-docker-compose_hackmd_1          /usr/local/bin/docker-entr ...   Up      0.0.0.0:3100->3000/tcp,:::3100->3000/tcp
growi-docker-compose_mariadb_1         docker-entrypoint.sh mysql ...   Up      3306/tcp
growi-docker-compose_mongo_1           docker-entrypoint.sh mongod      Up      27017/tcp
growi-docker-compose_plantuml_1        /docker-entrypoint.sh java ...   Up      0.0.0.0:8080->8080/tcp,:::8080->8080/tcp

elasticsearchがcode78 exitするとき

sudo sysctl -w vm.max_map_count=262144
  • Growiを再起動して再度確認する
docker-compose down
docker-compose up -d
docker-compose ps