Day-06-Deploy-https-website-part01

Ingress

Ingress 是對 Cluster 中服務的外部存取進行管理的 API 對象,典型的存取方式是 HTTP

Ingress Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: minimal-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx-example
rules:
- http:
paths:
- path: /testpath
pathType: Prefix
backend:
service:
name: test
port:
number: 80

Ingress 需要指定 apiVersionkindmetadataspec

Ingress rules

  • host: Optional - 若沒有設定 host 的話則代表所有 http request, 若是有設定 host 則代表描述的是該 host 的規則
  • path 列表 - 每一個 path 可以指向不同 或是同一個 service
  • service name 則需要對應到每一個 Service 的名稱

路徑類型

Ingress 中的每個路徑都需要有對應的路徑類型(Path Type)

未明確設定 pathType 的路徑無法通過合法性檢查。目前支援的路徑類型有三種

  • ImplementationSpecific: 對於這種路徑類型,匹配方法取決於 IngressClass。具體實作可以將其作為單獨的 pathType 處理或作與 Prefix 或 Exact 類型相同的處理
  • Exact:精確匹配 URL 路徑,且區分大小寫
  • Prefix:基於以 / 分隔的 URL 路徑前綴匹配。匹配區分大小寫, 並且對路徑中各個元素逐一執行匹配操作。路徑元素指的是由 / 分隔符號分隔的路徑中的標籤清單。如果每個 p 都是請求路徑 p 的元素前綴,則請求與路徑 p 相符。
1
2
Note: 如果路徑的最後一個元素是請求路徑中最後一個元素的子字串,則不會被視為符合
(例如:/foo/bar 符合 /foo/bar/baz, 但不符合 /foo/barbaz)

萬用字符號

主機名稱可以是精確匹配(例如 “foo.bar.com”)或使用通配符來匹配 (例如 “*.foo.com”)

精確比對要求 HTTP host 頭部欄位與 host 欄位值完全相符

通配符匹配則要求 HTTP host 頭部欄位與通配符規則中的後綴部分相同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-wildcard-host
spec:
rules:
- host: "foo.bar.com"
http:
paths:
- pathType: Prefix
path: "/bar"
backend:
service:
name: service1
port:
number: 80
- host: "*.foo.com"
http:
paths:
- pathType: Prefix
path: "/foo"
backend:
service:
name: service2
port:
number: 80

Day-05-ingress-controller

Ingress Controller

Ingress 是進入的意思

在 Kubernetes 中也是代表著進入 Cluster 的流量

Egress 則代表退出 Cluster 的流量

Ingress 是原生的 Kubernetes 的資源

使用 Ingress 可以維護 DNS routing 設定

如果沒有使用 Kubernetes Ingress 你需要增加一個 Loadbalancer

image

若是使用 Ingress 的話 如下圖

image1

Note: AWS GCP 雲端 Ingress Controller 的實作略有不同。例如,AWS loadbalancer 充當入口控制器。GKE Ingress Setup

在 Kubernetes Ingress 穩定前可以先用 Nginx 或是 HAproxy kubernetes 將流量導入 Cluster

Kubernetes Ingress 如何作用

主要有兩個概念

  1. Kubernetes Ingress 資源: Kubernetes Ingress 負責 Cluster 中的 DNS Routing 規則
  2. Kubernetes Ingress Controller: Kubernetes 入口控制器(Nginx/HAProxy 等)負責透過存取入口資源應用的 DNS 規則來進行路由

image3

實際範例

hello-one.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
apiVersion: v1
kind: Service
metadata:
name: hello-one
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
selector:
app: hello-one
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-one
spec:
replicas: 3
selector:
matchLabels:
app: hello-one
template:
metadata:
labels:
app: hello-one
spec:
containers:
- name: hello-ingress
image: nginxdemos/hello
ports:
- containerPort: 80

hello-two.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
apiVersion: v1
kind: Service
metadata:
name: hello-two
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
selector:
app: hello-two
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-two
spec:
replicas: 3
selector:
matchLabels:
app: hello-two
template:
metadata:
labels:
app: hello-two
spec:
containers:
- name: hello-ingress
image: nginxdemos/hello
ports:
- containerPort: 80

my-new-ingress.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-new-ingress
annotations:
kubernetes.io/ingress.class: nginx
spec:
rules:
- host: hello-one.tomas.website
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: hello-one
port:
number: 80
- host: hello-two.tomas.website
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: hello-two
port:
number: 80

完成之後可以打開頁面

hello-one.tomas.website

hello-two.tomas.website

明天再來繼續聊聊 Ingress Controller

Day-04-Deploy-to-cluster

Deploy to Cluster on LKE

建立一個 app 的資料夾

1
2
3
$ mkdir app
$ mv ./* ./app
$ mkdir manifests

建立一個 static-site-deployment.yaml 檔案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: apps/v1
kind: Deployment
metadata:
name: static-site-deployment
labels:
app: static-site
spec:
replicas: 3
selector:
matchLabels:
app: static-site
template:
metadata:
labels:
app: static-site
spec:
containers:
- name: static-site
image: horsekit1982/lke-example:v1.0.0
imagePullPolicy: Always
ports:
- containerPort: 80

Deployments 描述 PodReplicaSet

建立一個名為 static-site-deployment 的 Deployment (以 .metadata.name)

會以這個名稱為基礎 建立 ReplicaSetPod

此 Deployment 建立一個 ReplicaSet 會由三個 (.spec.replicas) Pod 副本

.spec.selector 描述了 ReplicaSet 如何尋找要管理的 Pod

在上面的範例中選擇 Pod 中定義的標籤(app: static-site)

Note: .spec.selector.matchLabels 欄位是 {key,value} 鍵值對映射。在 matchLabels 映射中的每個 {key,value} 映射等效於 matchExpressions 中的一個元素, 即其 key 字段是 “key”,operator 為 “In”,values 數組僅包含 “value”。在 matchLabels 和 matchExpressions 中給出的所有條件都必須滿足才能匹配。

template 自斷包含

  • Pod 使用 .metadata.labels 欄位打上 app: nginx 標籤
  • Pod 範本規約(即 .template.spec 欄位)指示 Pod 執行 static-site 容器
  • 建立容器並使用 .spec.template.spec.containers[0].name 欄位將其命名為 static-site

建立一個 static-site-service.yaml 檔案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: v1
kind: Service
metadata:
name: static-site-service
namespace: react-site
annotations:
service.beta.kubernetes.io/linode-loadbalancer-throttle: "4"
labels:
app: static-site
spec:
type: LoadBalancer
ports:
- name: http
port: 80
protocol: TCP
targetPort: 80
selector:
app: static-site
sessionAffinity: None

Kubernetes 中 Service 的一個關鍵目標是讓你無需修改現有應用程式以使用某種不熟悉的服務發現機制。

你可以在 Pod 集合中運行程式碼,無論程式碼是為雲端原生環境設計的, 還是被容器化的舊應用程式

你可以使用 Service 讓一組 Pod 在網路上訪問,這樣客戶端就能與之互動

上面的範例建立了一個 static-site-service 的服務

Port 是 80

Note: Service 能夠將任意入站 port 對應到某個 targetPort。預設情況下,出於方便考慮,targetPort 會被設定為與 port 欄位相同的值

也可以有另外一種範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app.kubernetes.io/name: proxy
spec:
containers:
- name: nginx
image: nginx:stable
ports:
- containerPort: 80
name: http-web-svc

---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app.kubernetes.io/name: proxy
ports:
- name: name-of-service-port
protocol: TCP
port: 80
targetPort: http-web-svc

在上述範例中 targetPort 指定是 http-web-svc 會指定到 Pod 的 port 80

Day-03-Container

Create React App

是一個自動化建立一個簡單的 React 的工具

利用這個工具產生一個靜態的檔案

再利用 docker 打包成 Image

並且以 container 跑起來 可以利用網頁瀏覽器瀏覽

Initial React Project

1
2
3
4
$ npx create-react-app lke-example
$ cd lke-example
$ npm install
$ npm start

react-demo

Dockerfile

Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
FROM node:18.0-slim as Frontend

WORKDIR /app
COPY . /app

RUN npm install
RUN npm run build

FROM nginx:stable-alpine
RUN mv /usr/share/nginx/html/index.html /usr/share/nginx/html/old-index.html
COPY --from=Frontend /app/build/ /usr/share/nginx/html/

EXPOSE 80

昨天有解釋一些參數

今天又多了一些新的參數

FROM node:18.0-slim as Frontend

這一個 image 基於 node:18.0-slim 產生後命名為 Frontend

COPY --from=Frontend /app/build/ /usr/share/nginx/html/

Frontend 的 image 複製 /app/build/ 的檔案到 Nginx 的 /usr/share/nginx/html/

Build Image

1
$ docker build -t lke-example .

成功後會有多一個 lke-example 的 image

Run a Container From lke-example

1
$ docker run -p 8080:80 -d lke-example

localhost

這時候就可以看到頁面

Push 到 Docker Hub

1
2
$ docker login # 登入
$ docker push lke-example ## 這時候可能會錯誤

Trouble shotting

denied: requested access to the resource is denied

如果看到這個錯誤

要給予這個 image 一個 tag

1
$ docker tag lke-example ${docker hub name}/lke-example:v1.0.0

之後再做 push 就可以了

推上去之後

明天再來處理 deploy 到 k8s 上

Day-02-Express與Docker

Express Hello world

Express 4.x

1
2
3
4
$ express --no-view helloworlddemo
$ cd helloworlddemo
$ npm install
$ npm start

檔案結構

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.
├── app.js
├── bin
│   └── www
├── package-lock.json
├── package.json
├── public
│   ├── images
│   ├── index.html
│   ├── javascripts
│   └── stylesheets
│   └── style.css
└── routes
├── index.js
└── users.js

以這個範例實作 Dockerfile

1
2
3
4
5
6
FROM node:18.0-slim
WORKDIR /app
COPY . .
RUN npm install
EXPOSE 3000
CMD ["node", "app.js"]

FROM

宣告基本的 Docker Image 環境

node:18.0-slim 詳情

Debian:11-slim 為基礎 Nodejs 版本18 的環境

WORKDIR

設定工作的資料夾位置

COPY

將檔案複製到 Docker Image 內的 WORKDIR 資料夾內

COPY SOURCE TARGET

Note: COPY和ADD二個的功用都一樣,就是將檔案複製進去image裡,COPY只能複製本機端的檔案或目錄,ADD能增加遠端url的檔案到docker image,ADD能順手將本機端複製進去的tar檔解開(遠端的tar不行!)。在實例上並不建議使用ADD來抓取網路上的檔案,會使用RUN curl or wget的方式。原因是使用一次ADD指令會增加docker image layers一次,原則上layers越多,docker image size就會越大!

RUN

建立image內部再跑的指令,是跑在Linux裡面的,跟ENTRYPOING與CMD最大的不同就是,他不是作為Image的「啟動指令」,而是作為image的「建造指令」

EXPOSE

很多人以為加上EXPOSE 3000,docker run起來後,就可以從本機端連得到container的 3000 port。

EXPOSE概念上比較像是在告訴使用這個image的人,服務是在那個port。

1
2
3
$ docker pull nginx
$ docker inspect nginx
$ docker run -p 8888:3000 -d nginx

CMD

在 Container 運行的時候 執行此命令

Note: ENTRYPOINT是Dockerfile 定義的一個指令,他的作用類似於CMD,都是在container 啟動時會自動執行的指令,你可以不定義CMD,然後改成定義ENTRYPOINT,你的container 照樣能夠啟動,如同你之前將命令寫在CMD 一樣

Day-01-Docker簡介

簡介

Docker 大致上可以分為 Image, Container, Registry 三個基本概念

  • Image - 是一個唯讀的完整操作系統環境,裡面僅僅安裝了必要的應用程序和支援程式. 它是一個模板
  • Container - 容器,鏡像運行時的實體,可以用一個image去啟動多個container,這些container是獨立的,互相不會干涉,我們對任何一個container做的改變,只會對那個container造成影響。
  • Registry - 如果是熟悉Git的,其實這裡說的Registry就是Git所說的Repository,只是跟Git repository不同的,Git repository是存放原始碼的,但是Docker registry是存放Docker images的.Docker跟Git的指令也有相似之處,譬如說push和pull,可以把 images 從Docker registry上傳或下載,概念是這樣,先有個大概的認識,之後就可以慢慢來實作了。

Docker Wiki

過往在做部署的時候總是一台 機器會架設多個服務,

服務之間的環境設定 或是套件 服務的版本會有所衝突

常常造成一些奇怪的問題

Docker 可以將環境單純化

盡量降低因為環境因素影響服務運行

React Native-fastlane-Android

Fastlane Install

1
2
3
4
$ bundle config set path 'vendor/bundle'
$ cd android
$ bundle install
$ bundle exec fastlane init

Build Android

FastFile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
default_platform(:android)

platform :android do
desc "Runs all the tests"
lane :test do
gradle(task: "test")
end

desc "Submit a new Beta Build to Crashlytics Beta"
lane :beta do
gradle(task: "clean assembleRelease")
# crashlytics
gradle(
task: 'assemble',
build_type: 'Release'
)
# sh "your_script.sh"
# You can also use other beta testing services here
end
end

可能會有錯誤 Expiring Daemon because JVM heap space is exhausted

Solution

~/.gradle/gradle.properties

1
2
3
org.gradle.daemon=true
org.gradle.configureondemand=true
org.gradle.jvmargs=-Xmx4g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8

./android/app/build.gradle

1
2
3
4
5
6
7
...
android {
dexOptions {
javaMaxHeapSize "3g"
}
}
...

Distribution with firebase

install plugin

1
$ bundle exec fastlane add_plugin firebase_app_distribution

login

1
$ bundle exec fastlane run firebase_app_distribution_login

修改 Fastfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
default_platform(:android)

platform :android do
desc "Runs all the tests"
lane :test do
gradle(task: "test")
end

desc "Submit a new Beta Build to Crashlytics Beta"
lane :beta do
gradle(task: "clean assembleRelease")
# crashlytics
gradle(
task: 'assemble',
build_type: 'Release'
)

firebase_app_distribution(
app: "firebase app project id",
groups: "firebase test groups"
)
end
end
1
$ bundle exec fastlane beta

參考資料

Expiring Daemon because JVM heap space is exhausted

React-Native-Fastlane-IOS

Setting Up fastlane

Install Homebrew

1
2
3
4
$ /usr/bin/ruby -e \
"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
$ brew update && brew install ruby
$ brew link --overwrite ruby

重新啟動 Terminal

1
2
3
4
5
6
$ sudo gem install bundler
$ xcode-select --install
$ sudo gem install -n /usr/local/bin fastlane --verbose
$ brew cask install fastlane
$ npx react-native init rndemo
$ cd rndemo/ios

add to .zshrc

1
2
export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8

Gemfile

1
2
3
source "https://rubygems.org"

gem "fastlane"
1
2
$ sudo gem install bundler
$ bundle exec fastlane init

screenshots

distribution to beta testing services

automate the App Store release process

setup code signing with fastlane

依據上述的 tutorial 就可以完成相對應的工作

React-create-rxdb-example

Install and Initial

1
2
$ npx create-react-app rxdbdemo
$ npm install --save concurrently moment pouchdb-adapter-http pouchdb-adapter-idb pouchdb-server react-toastify rxdb rxjs serve

package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
{
"name": "rxdbdemo",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"concurrently": "^5.3.0",
"moment": "^2.29.1",
"pouchdb-adapter-http": "^7.2.2",
"pouchdb-adapter-idb": "^7.2.2",
"pouchdb-server": "^4.2.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.3",
"react-toastify": "^6.0.9",
"rxdb": "^9.6.0",
"rxjs": "^6.6.3",
"serve": "^11.3.2"
},
"scripts": {
"start": "concurrently \"npm run server\" \"react-scripts start\"",
"server": "pouchdb-server -d ./db",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
1
$ yarn start

會開啟一個本地的 db

可以透過 pounch-db

Create Schema

src/Schema.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const schema = {
title: 'Anonymous chat schema',
description: 'Database schema for an anonymous chat',
version: 0,
type: 'object',
properties: {
id: {
type: 'string',
primary: true
},
message: {
type: 'string'
}
},
required: ['message']
}

export default schema;

App.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import React, { useEffect, useRef, useState } from "react";
import logo from "./logo.svg";
import "./App.css";
import 'react-toastify/dist/ReactToastify.css';
import { ToastContainer, toast } from "react-toastify";
import * as moment from "moment";
import * as RxDB from "rxdb";
import schema from "./Schema";

let localdb = null;
const dbName = "test";
const syncURL = "http://localhost:5984/";

RxDB.addRxPlugin(require("pouchdb-adapter-idb"));
RxDB.addRxPlugin(require("pouchdb-adapter-http"));

const createDatabase = async () => {
try {
const db = await RxDB.createRxDatabase({
name: dbName,
adapter: "idb",
password: "12345678",
});

db.waitForLeadership().then(() => {
document.title = "♛ " + document.title;
});

const messagesCollection = await db.collection({
name: "messages",
schema: schema,
});

messagesCollection.sync({ remote: syncURL + dbName + "/" });
console.log("createDatabase -> messagesCollection", messagesCollection);

return db;
} catch (error) {
console.log("createDatabase -> error", error);
}
};

const addMessage = async (message, setNewMessage) => {
console.log("addMessage -> localdb", localdb)
const id = Date.now().toString();
const newMessage = { id, message };

await localdb.messages.insert(newMessage);

setNewMessage("");
};

const renderMessages = (messages) =>
messages.map(({ id, message }) => {
const date = moment(id, 'x').fromNow();
return (
<div key={id}>
<p>{date}</p>
<p>{message}</p>
<hr />
</div>
)
});
function App() {
let dbRef = useRef(null);
const [messages, setMessages] = useState([]);
const [subs, setSubs] = useState([]);
const [newMessage, setNewMessage] = useState("");

useEffect(() => {
createDatabase().then((db) => {
localdb = db;

const sub = db.messages
.find()
.sort({ id: 1 })
.$.subscribe((msgs) => {
if (!msgs) return;
toast("Reloading messages");
setMessages(msgs);
});
setSubs([...subs, sub]);
});

return () => {
subs.forEach((sub) => sub.unsubscribe());
};
}, []);
return (
<div className="App">
<ToastContainer autoClose={3000} />
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>

<div>{renderMessages(messages)}</div>

<div id="add-message-div">
<h3>Add Message</h3>
<input
type="text"
placeholder="Message"
value={newMessage}
onChange={(env) => setNewMessage(env.target.value)}
/>
<button onClick={() => addMessage(newMessage, setNewMessage)}>
Add message
</button>
</div>
</div>
);
}

export default App;

React-Native-Cache-PartII

建立雙向鍊表的 Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Node {
constructor(data) {
this.data = data;
this.prev = null;
this.next = null;
}
}

class DoubleLinklist {
constructor() {
this.head = null;
this.tail = null;
this.length = 0;
}

append(data){
const newNode = new Node(data);

console.log('this.length', this.length);
console.log('this', this);
if (this.length === 0) {
this.tail = newNode;
this.head = newNode;
} else {
newNode.prev = this.tail;
this.tail.next = newNode;
this.tail = newNode;
}

this.length += 1;
};
}

let list = new DoubleLinklist();

list.append('aaa');
list.append('bbb');
list.append('ccc');
console.log(list);

情境一

image

情境二

image

image

結果

  • next

image

  • prev

image

完整範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
class Node {
constructor(data) {
this.data = data;
this.prev = null;
this.next = null;
}
}

class DoubleLinklist {
constructor() {
this.head = null;
this.tail = null;
this.length = 0;
}

toString() {
return this.backwardString();
}

forwardString() {
let current =this.tail;
let resultString = "";

while (current) {
resultString += current.data + "--";
current = current.prev;
}
return resultString;
}

backwardString() {
let current = this.head;
let resultString = "";

while (current) {
resultString += current.data + "--";
current = current.next;
}
return resultString;
}

indexOf(data){
let current = this.head;
let index = 0;

while(current){
if (current.data == data) {
return index;
}
current = current.next;
index += 1;
}
return -1;
}

removeAt(position){
if (position < 0 || position >= this.length) {
return null;
}

let current = this.head;
if (this.length == 1) {
this.head = null;
this.tail = null;
} else{
if (position == 0) {
this.head.next.prev = null;
this.head = this.head.next;

}else if(position == this.length - 1){
current = this.tail;
this.tail.prev.next = null;
this.tail = this.tail.prev;
}else{
let index = 0;
while(index++ < position){
current = current.next;
}
current.next.prev = current.prev;
current.prev.next = current.next;
}
}

this.length -= 1;
return current.data;
}

remove(data) {
const index = this.indexOf(data);
return this.removeAt(index);
}

isEmpty(){
return this.length == 0;
}

size() {
return this.length;
}

getHead(){
return this.head.data;
}

getTail (){
return this.tail.data;
}

insert(position, data) {
if (position < 0 || position > this.length) return false

let newNode = new Node(data);

if (this.length == 0) {
this.head = newNode;
this.tail = newNode;
}else {
if (position == 0) {
this.head.prev = newNode;
newNode.next = this.head;
this.head = newNode;

} else if(position == this.length){
this.tail.next = newNode;
newNode.prev = this.tail;
this.tail = newNode;
}else{
let current = this.head;
let index = 0;
while(index++ < position){
current = current.next;
}
newNode.next = current;
newNode.prev = current.prev;
current.prev.next = newNode;
current.prev = newNode;
}
}

this.length += 1;
return true;
}

append(data){
const newNode = new Node(data);

console.log('this.length', this.length);
console.log('this', this);
if (this.length === 0) {
this.tail = newNode;
this.head = newNode;
} else {
newNode.prev = this.tail;
this.tail.next = newNode;
this.tail = newNode;
}

this.length += 1;
};
}

let list = new DoubleLinklist();
list.append('a')
list.append('b')
list.append('c')
list.append('d')

console.log(list.remove('a'));
console.log(list);
console.log(list.isEmpty());
console.log(list.size());
console.log(list.getHead());
console.log(list.getTail());
|